问题
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