4

Rust 関数オーバーロード、引数の数が異なる場合

 2 years ago
source link: https://dev.to/fmtweisszwerg/rust-guan-shu-obarodo-yin-shu-noshu-gayi-naruchang-he-22fb
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

対象バージョン

$ rustc --version
rustc 1.55.0 (c8dfcfe04 2021-09-06)
Enter fullscreen modeExit fullscreen mode

下記のように「引数の数が異なる場合」のオーバーロードはマクロを使用します。
正確には「マクロを使うことでオーバーロードされているように見える」というものになります。

前回のようにジェネリクスを使うことでも一応可能ではありますが、プレースホルダーを使用する必要があり、関数定義でも呼び出しでも微妙なことになってしまいます。

fn hoge(arg0, arg1) {
...
}

fn hoge(arg) {
...
}
Enter fullscreen modeExit fullscreen mode

マクロで疑似オーバーロード

下記の構造体を例に考えます。

struct SampleStruct {
    ip_address: IpAddr,
    port_number: u16,
    sample_socket: socket2::Socket,
}
Enter fullscreen modeExit fullscreen mode

この構造体について2つのコンストラクタを実装することにします

  1. IP アドレスを文字列で受け取るコンストラクタ
  2. std::net::SocketAddr 構造体のインスタンスを受け取るコンストラクタ

これを実現するマクロは次のようになります。

macro_rules! SampleStruct_new {
    ($str_ip:expr , $num_port:expr) => ({
        let ip: IpAddr = $str_ip.parse::<IpAddr>()
            .unwrap_or_else( |_| { panic!("`address` MUST be an IPv4 address or IPv6 address.") });

        SampleStruct {
            ip_address: ip,
            port_number: $num_port,
            sample_socket: Socket::new(
                if ip.is_ipv4() { Domain::IPV4 } else { Domain::IPV6 },
                Type::DGRAM,
                Some(Protocol::UDP)
            ).unwrap()
        }
    });

    ($obj_SocketAddr:expr) => ({
        SampleStruct {
            ip_address: $obj_SocketAddr.ip(),
            port_number: $obj_SocketAddr.port(),
            sample_socket: Socket::new(
                if $obj_SocketAddr.ip().is_ipv4() { Domain::IPV4 } else { Domain::IPV6 },
                Type::DGRAM,
                Some(Protocol::UDP)
            ).unwrap()
        }
    });
}
Enter fullscreen modeExit fullscreen mode

このマクロを呼び出す main() 関数は次のように書けます。

fn main() {
    let foo = SampleStruct_new!("127.0.0.1", 12345);
    println!("IP address = {}, Port = {}", foo.ip_address.to_string(), foo.port_number);

    let ipv6_sock_addr = SocketAddr::new("::1".parse::<IpAddr>().unwrap(), 12345);
    let bar = SampleStruct_new!(ipv6_sock_addr);
    println!("IP address = {}, Port = {}", bar.ip_address.to_string(), bar.port_number);
}
Enter fullscreen modeExit fullscreen mode
IP address = 127.0.0.1, Port = 12345
IP address = ::1, Port = 12345
Enter fullscreen modeExit fullscreen mode

コード全体は Rust Playground へどうぞ。

ジェネリクスを使用する場合

ジェネリクスを使用する場合はまずトレイトを宣言します。

trait SampleTrait<T, O> {
    fn new(address:T, port:O) -> SampleStruct;
}
Enter fullscreen modeExit fullscreen mode

上記宣言に対して実装します。詳細な説明は省きますが 使わない引数の部分に _:() というプレースホルダーを置く と覚えてください。

impl SampleTrait<&str, u16> for SampleStruct {
    fn new(address: &str, port: u16) -> Self {
        let addr = address.to_string().parse::<IpAddr>()
            .unwrap_or_else( |_| { panic!("`address` MUST be an IPv4 address (dotted-decimal form) or an IPv6 address.") });

        return SampleStruct {
            ip_address: addr,
            port_number: port,
            sample_socket: Socket::new(
                if addr.is_ipv4() { Domain::IPV4 } else { Domain::IPV6 },
                Type::DGRAM,
                Some(Protocol::UDP)
            ).unwrap()
        }
    }
}

impl SampleTrait<SocketAddr, ()> for SampleStruct {
    fn new(address: SocketAddr, _:()) -> Self {
        return SampleStruct {
            ip_address: address.ip(),
            port_number: address.port(),
            sample_socket: Socket::new(
                if address.ip().is_ipv4() { Domain::IPV4 } else { Domain:: IPV6 },
                Type::DGRAM,
                Some(Protocol::UDP)
            ).unwrap()
        }
    }
}
Enter fullscreen modeExit fullscreen mode

呼び出すときはプレースホルダーを指定した部分にの値として () を置きます。

fn main() {
    let hoo = SampleStruct::new("127.0.0.1", 12345);
    println!("IP address = {}, Port = {}", hoo.ip_address.to_string(), hoo.port_number);

    let ipv6_sock_addr = SocketAddr::new("::1".parse::<IpAddr>().unwrap(), 12345);
    let baz = SampleStruct::new(ipv6_sock_addr, ());
    println!("IP address = {}, Port = {}", baz.ip_address.to_string(), baz.port_number);
}
Enter fullscreen modeExit fullscreen mode
IP address = 127.0.0.1, Port = 12345
IP address = ::1, Port = 12345
Enter fullscreen modeExit fullscreen mode

このようにジェネリクス + プレースホルダー _:() でもできなくはないのですが、見た目があまりよくないものになってしまいます。

コード全体は Rust Playground で確認してください。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK