Как инициализировать структуру с помощью ряда аргументов

Во многих языках общей конструкторской идиомой является инициализация значений объекта с использованием синтаксиса, подобного этому псевдокоду:

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

Сначала кажется, что ржавчина не является исключением. Много 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 ответа

Решение

Это невозможно с макросом по очень простой причине: макрос не может вызвать имена полей из воздуха.

Самое простое решение, если вам удобно раскрывать детали вашего типа, это сделать поля общедоступными:

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 ,

Однако я хотел бы отметить, что это намного более подвержено ошибкам, так как поменять местами два аргумента, не заметив этого, гораздо проще. Вы можете придерживаться явных имен или вместо этого вводить явные типы.

Я не уверен, что вы можете (или, возможно, должны) полагаться на порядок полей структуры в макросе.

Но, возможно, похоже на то, что вы хотите, в том, что он экономит шаблон конструктора, является derive_builder ящик

Вы можете использовать это так:

#[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"
Другие вопросы по тегам