如何使用一系列参数初始化结构

时间:2017-05-25 01:06:15

标签: rust rust-macros

在许多语言中,常见的构造函数习惯是使用类似伪代码的语法初始化对象的值:

constructor Foo(args...) {
    for arg {
        object.arg = arg
    }
}
首先,Rust似乎也不例外。 impl的许多struct包含一个名为new的构造函数,用于将有序的参数序列压缩到结构的字段中:

struct Circle {
    x: i32,
    y: i32,
    radius: i32,
}

impl Circle {
    fn new(x: i32, y: i32, radius: i32) -> Circle {
        Circle { x: x, y: y, radius: radius }
    }
}

使用宏执行此操作可能看起来像zip!(Circle, 52, 32, 5)。它会按顺序将值压缩到Circle的字段中。 zip!(Circle, 52, 32)zip!(Circle, 52, 32, 5, 100)都会出现问题,但像这样的宏将是一种非常灵活的方法,可以在没有太多样板的情况下将值推送到任何结构的新实例上。

是否有一种惯用的方法来简化此样板?如何在不明确编写样板代码的情况下将一系列有序参数映射到结构的每个字段上?

2 个答案:

答案 0 :(得分:4)

由于一个非常简单的原因,宏无法做到这一点:宏无法凭空想象字段名称。

最简单的解决方案是,如果您愿意公开您的类型的详细信息,那就是公开字段:

struct Circle {
    pub x: i32,
    pub y: i32,
    pub radius: i32,
}

fn main() {
    let circle = Circle { x: 3, y: 4, radius: 5 };
}

也就是说,没有需要来构建一个构造函数,没有一个构造函数就可以正常工作。

毕竟,如果构造函数除了传递值之外什么都不做,那么构造函数本身就没有意义了,不是吗?

如果您希望提供更短的初始化语法,您可以例如:

use std::convert::From;

impl From<(i32, i32, i32)> for Circle {
    fn from(t: (i32, i32, i32)) -> Circle {
        Circle { x: t.0, y: t.1, radius: t.2 }
    }
}

fn main() {
    let circle: Circle = (3, 4, 5).into();
}

通常,类型推断可以减轻你拼出: Circle的费用。

但是我会注意到这更容易出错,因为在没有注意的情况下交换两个参数要容易得多。您可能希望坚持使用显式名称,或者引入显式类型。

答案 1 :(得分:3)

我不确定你是否可以(或者应该)依赖宏中结构的字段顺序。

但也许类似于你想要的,因为它保存了构造函数样板,是derive_builder crate

你可以像这样使用它:

#[macro_use]
extern crate derive_builder;

#[derive(Builder, Debug)]
struct Circle {
    x: i32,
    y: i32,
    radius: i32,
}

fn do_stuff() -> Result<(), String> {
    let c = CircleBuilder::default()
        .x(2)
        .y(4)
        .radius(123)
        .build()?;

    println!("     x = {}", c.x);
    println!("     y = {}", c.y);
    println!("radius = {}", c.radius);

    Ok(())
}

请注意调用函数中的Result和调用?后的build()

请确保在Cargo.toml

中加入此内容
[dependencies]
derive_builder = "0.4.7"