Rust 関数オーバーロード、引数の数が異なる場合
Tags: rust, programming
Published: 2021-10-15 18:53:36

---
title: Rust 関数オーバーロード、引数の数が異なる場合
published: true
description: `fn foo(arg0, arg1)` と `fn foo(arg0)` のように引数の数が異なる場合のオーバーロード方法を解説します
tags: rust, programming,
//cover_image: https://direct_url_to_image.jpg
---

## 対象バージョン

```
$ rustc --version
rustc 1.55.0 (c8dfcfe04 2021-09-06)
```

## 概要

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

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

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

fn hoge(arg) {
...
}
```

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

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

```rust
struct SampleStruct {
    ip_address: IpAddr,
    port_number: u16,
    sample_socket: socket2::Socket,
}
```

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

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

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

```rust
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()
        }
    });
}
```

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

```rust
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);
}
```

実行結果

```
IP address = 127.0.0.1, Port = 12345
IP address = ::1, Port = 12345
```

コード全体は [Rust Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=3d35da870934e08ba41e787237df5ede) へどうぞ。


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

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

```rust
trait SampleTrait<T, O> {
    fn new(address:T, port:O) -> SampleStruct;
}
```

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

```rust
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()
        }
    }
}
```

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

```rust
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);
}
```

実行結果

```
IP address = 127.0.0.1, Port = 12345
IP address = ::1, Port = 12345
```

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

コード全体は [Rust Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=958e0fb6ef45fd918c0011555d1f1add) で確認してください。