Rust 参数设置默认值

Option 模式?

在调用函数的时候,可能参数比较多,但是我们只需要修改其中的几个特定参数就可以满足我们的需求,而其他的参数我们希望保持不变,但是 Rust 和 Go 一样,函数调用中不支持默认参数,所以我们必须另寻他法,那么第一感觉我们是不是可以采用 Go 中的 选项模式呢?

虽然 Rust 函数中并不支持变参数传递,但是我们可以通过 来间接达到相同的功能,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#[derive(Debug)]
pub struct Server {
host: String,
port: i16,
}

impl Default for Server {
fn default() -> Self {
Self {
host: "127.0.0.1".to_string(),
port: 8888,
}
}
}

macro_rules! NewServer {
( $($f:ident), *) => {{
let mut s = Server {
..Default::default()
};

$(
$f(&mut s);
)*

s
}};
}

我们只要传递函数给宏,函数中修改参数的指即可,但是我们如何将函数传递进去呢,这里我们采用 FnOnce 闭包函数

1
2
3
4
5
6
7
fn with_host(host: String) -> impl FnOnce(&mut Server) {
move |server| server.host = host
}

fn with_port(port: i16) -> impl FnOnce(&mut Server) {
move |server| server.port = port
}

调用的时候不太方便,因为宏中不支持使用括号,所以必须使用下面的方式进行调用

1
2
3
4
5
6
7
#[test]
fn test() {
let f = with_host(String::from("0.0.0.0"));
let f2 = with_port(8000);
let s = NewServer![f, f2];
println!("{:?}", s);
}

函数重载

在 Rust 中实际上并不支持函数重载,但是通过宏,我们可以达到类似的效果,但同时由于宏的替换方式,所以我们无法通过判断变量的类型,然后动态返回结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#[derive(Debug)]
pub struct Server {
host: String,
port: i16,
}

#[macro_export]
macro_rules! NewServer {
() => {
Server {
host: String::from("127.0.0.1"),
port: 8080,
}
};

($var: expr) => {
Server {
host: String::from($var),
port: 8080,
}
};
(_, $var: expr) => {
Server {
host: String::from("127.0.0.1"),
port: $var,
}
};
}

#[test]
fn test() {
let s = NewServer!();
println!("{:?}", s);
let s = NewServer!("0.0.0.0");
println!("{:?}", s);
let s = NewServer!(_, 8888);
println!("{:?}", s);
}

手动实现 setter

创建对象的时候返回一个结构体,然后提供 setter 方法进行配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#[derive(Debug)]
pub struct Server {
host: String,
port: i16,
}

impl Server {
pub fn new() -> Self {
Self {
host: String::from("127.0.0.1"),
port: 8888,
}
}

pub fn host(mut self, host: &str) -> Self {
self.host = host.to_string();
self
}

pub fn port(mut self, port: i16) -> Self {
self.port = port;
self
}
}

#[test]
fn test_new_server() {
let server = Server::new().host("0.0.0.0").port(8080);
println!("{:?}", server);
}

使用 crate

自己手动写了一个 derive macro,可以自动生成 gettersetter,代码仓库见 construct,在 Cargo.toml 中进行引入:

1
2
[dependencies]
construct = {git = "https://github.com/junhaideng/construct"}

然后创建结构体的时候添加 derive(Constructor) 即可,详细使用方式见代码仓库说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
mod server {
use construct::Constructor;

#[derive(Debug, Constructor)]
pub struct Server {
#[cons(setter = false, rename_getter = get_host)]
host: String,
#[cons(getter = false, rename_setter = set_server_port)]
port: u16,
}

impl Server {
pub fn new() -> Self {
Self {
host: String::from("127.0.0.1"),
port: 8080,
}
}
}
}

#[test]
fn test() {
use crate::server::Server;
let mut s = Server::new();
println!("host: {}", s.get_host());

// not implement because rename
// println!("port: {}", s.port());
s.set_server_port(10);
println!("{:?}", s);
}

Builder 模式

与之类似的方式,其实我们也可以通过 Builder(创建者) 模式达到相同的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
struct Builder {
host: String,
port: u16,
}

struct Server {
host: String,
port: u16,
}

impl Builder {
pub fn new() -> Self {
Self::default()
}
pub fn host(mut self, host: &str) -> Self {
self.host = host.to_string();
self
}
pub fn port(mut self, port: u16) -> Self {
self.port = port;
self
}
pub fn build(self) -> Server {
Server {
host: self.host,
port: self.port,
}
}
}

impl Default for Builder {
fn default() -> Self {
Self {
host: "127.0.0.1".to_string(),
port: 8080,
}
}
}


生活杂笔,学习杂记,偶尔随便写写东西。

作者

Edgar

发布于

2022-07-27

更新于

2022-09-03

许可协议

评论