How to initialize a struct with a series of arguments

你。 提交于 2021-02-08 04:09:42

问题


In many languages, a common constructor idiom is to initialize values of an object using syntax like this pseudocode:

constructor Foo(args...) {
    for arg {
        object.arg = arg
    }
}

Rust at first seems to be no exception. Many impl for a struct include a constructor named new to zip an ordered series of arguments onto the fields of the struct:

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 }
    }
}

Doing this with a macro might look like zip!(Circle, 52, 32, 5). It would zip the values, in order, onto the fields of Circle. Both zip!(Circle, 52, 32) or zip!(Circle, 52, 32, 5, 100) would present issues, but a macro like this would be a very flexible way to push values onto a new instance of any struct without so much boilerplate.

Is there an idiomatic way to simplify this boilerplate? How is it possible to map a series of ordered arguments onto each field of a struct without explicitly writing the boilerplate code to do so?


回答1:


This is not possible with a macro for a very simple reason: a macro cannot conjure the field names out of thin air.

The simplest solution, if you are comfortable exposing the details of your type, is making the fields public:

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

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

That is, there is no need to have a constructor, it works perfectly fine without one.

After all, if the constructor does nothing else than passing on the values, the constructor itself is rather pointless, isn't it?

If you wish to offer a shorter initialization syntax, you can for example:

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

And normally, type inference should relieve you from having to spell out : Circle.

I would note however that this is much more error-prone, as swapping two of the arguments without noticing is much easier. You may want to stick to explicit names, or instead introduce explicit types.




回答2:


I'm not sure you can (or perhaps should) rely on the order of the fields of a struct in a macro.

But perhaps similar to what you want, in that it saves constructor boilerplate, is the derive_builder crate.

You can use it like this:

#[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(())
}

Note the Result in the calling function and the ? after the call to build().

Make sure to have this in your Cargo.toml:

[dependencies]
derive_builder = "0.4.7"


来源:https://stackoverflow.com/questions/44170735/how-to-initialize-a-struct-with-a-series-of-arguments

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!