対象バージョン
$ rustc --version
rustc 1.55.0 (c8dfcfe04 2021-09-06)
概要
下記のように「引数の数が異なる場合」のオーバーロードはマクロを使用します。
正確には「マクロを使うことでオーバーロードされているように見える」というものになります。
前回のようにジェネリクスを使うことでも一応可能ではありますが、プレースホルダーを使用する必要があり、関数定義でも呼び出しでも微妙なことになってしまいます。
fn hoge(arg0, arg1) {
...
}
fn hoge(arg) {
...
}
マクロで疑似オーバーロード
下記の構造体を例に考えます。
struct SampleStruct {
ip_address: IpAddr,
port_number: u16,
sample_socket: socket2::Socket,
}
この構造体について2つのコンストラクタを実装することにします
- IP アドレスを文字列で受け取るコンストラクタ
- 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()
}
});
}
このマクロを呼び出す 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);
}
実行結果
IP address = 127.0.0.1, Port = 12345
IP address = ::1, Port = 12345
コード全体は Rust Playground へどうぞ。
ジェネリクスを使用する場合
ジェネリクスを使用する場合はまずトレイトを宣言します。
trait SampleTrait<T, O> {
fn new(address:T, port:O) -> SampleStruct;
}
上記宣言に対して実装します。詳細な説明は省きますが 使わない引数の部分に _:()
というプレースホルダーを置く と覚えてください。
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()
}
}
}
呼び出すときはプレースホルダーを指定した部分にの値として ()
を置きます。
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 で確認してください。
Top comments (0)