Rust结构体使用具名参数

创建于 2024年7月25日修改于 2024年7月25日
RustStruct

考虑以下Rust代码段,一个 Target 结构体,以及一个相应的构造者 Builder

struct Target {
    foo: bool,
    bar: u64,
    baz: i64,
}

struct Builder {
    foo: Option<bool>,
    bar: Option<u64>,
    baz: Option<i64>,
}

impl Builder {
    fn new() -> Builder {
        return Builder {
            foo: None,
            bar: None,
            baz: None,
        };
    }

    fn foo(mut self, v: bool) -> Builder {
        self.foo = Some(v);
        return self;
    }

    fn bar(mut self, v: u64) -> Builder {
        self.bar = Some(v);
        return self;
    }

    fn baz(mut self, v: i64) -> Builder {
        self.baz = Some(v);
        return self;
    }

    fn build(self) -> Target {
        return Target {
            foo: self.foo.unwrap_or(false),
            bar: self.bar.unwrap_or(0),
            baz: self.baz.unwrap_or(0),
        };
    }
}

这段代码让你可以构建一个对象。

let t = Builder::new().foo(true).bar(2).baz(3).build();

不过,这并不算很有意思。除了必须表达的构造者之外,我们似乎并没有在表达我们的主要目标!

让我们采用一种构造模式(T)来表达我们的主要目标,并且看起来像是通过具名参数进行赋值。

macro_rules! T {
    ($($n:ident = $e:expr),+) => {
        Builder::new()$(.$n($e))*.build()
    };
}

现在你在表达你的主要目标了。很多项目中它们高频使用,以至于值得用单字母的宏来表示:

let t = T!(foo=true, bar,2, baz=3);

这显得好一些。具名参数,让你知道自己传递了什么,虽然它底层还是一个构造者模式。

不像直接构造结构体,这种模式允许你只传递你需要的内容。

let t = T!(bar=2);
assert_eq!(
    t,
    Target {
        foo: false,
        bar: 2,
        baz: 0
    }
)

有人可能会说,Rust 中的许多构建器都是允许失败的。它们通常使用 Result 类型来完成构建。

让我们考虑一下这个问题:

struct TryBuilder {}

impl TryBuilder {
    fn new() -> TryBuilder {
        return TryBuilder {};
    }
    fn bar(mut self, v: u64) -> Self {
        return self;
    }

    fn build(mut self) -> Result<Target, String> {
        return Err("Couldn't build".to_string());
    }
}

我们将“尝试”构建一个 T,而不是直接构建:

macro_rules! tryT {
    ($($n:ident = $e:expr),+) => {
        TryBuilder::new()$(.$n($e))*.build()?
    };
}

上面的宏让我免费获得了类似异常处理的行为。省去一个 ? 号!

fn try_u64_from_t() -> Result<u64, String> {
    let t = tryT!(bar = 3);
    return Ok(t.bar);
}

tryT 自动用 ? 将表达式解包,所以得到的 t 已经被解包了。当然,这不算啥优点,因为 ? 本身已经非常好用且出色了!

业界一直有很多理由不推荐使用宏,人们认为它难以理解、不好调试等等。但我们都不好意思承认的最大原因只是品味:宏有它特定的风格,稍微设置不当就会让人感觉很不舒服。

但是如果你是在自己的代码库中工作,那就尽量满足自己,随心所欲吧!一个有很多宏的代码库并不是天生邪恶。毕竟我们不是在 C 语言中,这些东西还是相当有原则的。

原文:https://rtpg.co/2024/07/22/rust-named-arguments/