如何为&Struct实现默认值?

5
在多次阅读Rust书后,我认为我开始理解生命周期的要点了,但对我而言,另一个问题是我们需要使用的语法来声明它们。我发现这真的很不直观。
我已经将我的一些简单的代码简化为这两个结构体(其中一个引用另一个)。
#[derive(Debug, Default)]
pub struct TestStructA {
    pub byte_1: u8,
    pub byte_2: u8,
    pub vector: Vec<u8>,
}

impl<'a> Default for &'a TestStructA {
    fn default() -> &'a TestStructA {
        &TestStructA { byte_1: 10, byte_2: 20, vector: 'a vec![1, 2, 3] }
    }
}

#[derive(Debug, Default)]
pub struct TestStructB<'a> {
    pub test_array: &'a [u8],
    pub t_a: &'a TestStructA,
}

如果您将此隔离的代码复制并粘贴到 main.rs 文件中并进行编译,则会出现以下错误:
error: expected `while`, `for`, `loop` or `{` after a label
  --> src/main.rs:10:59
   |
10 |         &TestStructA { byte_1: 10, byte_2: 20, vector: 'a vec![1, 2, 3] }
   |                                                           ^^^ expected `while`, `for`, `loop` or `{` after a label

error: labeled expression must be followed by `:`
  --> src/main.rs:10:59
   |
10 |         &TestStructA { byte_1: 10, byte_2: 20, vector: 'a vec![1, 2, 3] }
   |                                                        ---^^^^^^^^^^^^^
   |                                                        | |
   |                                                        | help: add `:` after the label
   |                                                        the label
   |
   = note: labels are used before loops and blocks, allowing e.g., `break 'label` to them

如果我不注释矢量参数的生命周期,我会得到期望的: "检查你的生命周期, 伙计"(这很有道理)。
error[E0515]: cannot return reference to temporary value
  --> src/main.rs:10:9
   |
10 |         &TestStructA { byte_1: 10, byte_2: 20, vector: vec![1, 2, 3] }
   |         ^-------------------------------------------------------------
   |         ||
   |         |temporary value created here
   |         returns a reference to data owned by the current function

当然,如果我选择一个更激烈的解决方案,从我的结构中删除“vector”属性,因为所有其他属性都是标量,代码就可以编译。但我需要我的结构具有某种非标量数据结构。我怀疑我需要在Default初始化器中对我的向量进行某种寿命标记,但我不确定是什么。
顺便说一下,我认为我的TestStructB也已经正确地注释了生命周期,但可能不是。它看起来正确吗?
最后,我之所以到这里,是因为编译器说我需要为原始结构的引用(&)版本声明一个默认初始化器。这个原始结构已经在我的程序中快乐地使用了,但从来没有被引用过。每当我开始以&引用方式使用它时,Rust都声称它需要其默认初始化器。问题是,我仍然认为在使用我的引用结构时做错了什么,并且有一种使用&引用(和生命周期注释)的结构的正确方法,而不会让Rust抱怨它们(不存在的)默认初始化器。
编辑:@JohnKugelman是正确的,我通过与Rust编译器的典型新手斗争来遇到这个“异常情况”,试图克服它产生的主要是晦涩难懂的消息。
我的简化原始内容如下:
#[derive(Debug, Default)]
pub struct TestStructA {
    pub byte_1: u8,
    pub byte_2: u8,
    pub vector: Vec<u8>,
}

#[derive(Debug, Default)]
pub struct TestStructB<'a> {
    pub test_array: &'a [u8],
    pub t_a: &'a TestStructA,
}

使用这段代码,我得到的错误是:

error[E0277]: the trait bound `&TestStructA: Default` is not satisfied
  --> src/main.rs:11:5
   |
11 |     pub t_a: &'a TestStructA,
   |     ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Default` is not implemented for `&TestStructA`
   |
   = help: the following implementations were found:
             <TestStructA as Default>
   = note: required by `std::default::Default::default`
   = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

显然,rustc误导我认为我需要&Struct默认初始化器?不能说...


2
impl<'a> Default for &'a TestStructA -- 你是如何想到这个的?让我们往回追溯一下,找出你认为需要它的原因,因为这看起来非常可疑。我认为你应该删除整个代码块,然后询问我们导致你到达这里的原始错误。 - John Kugelman
顺便说一下,我认为我的TestStructB也已经正确地标注了生命周期,但也许不是。如果不知道TestStructB的用途,很难说它是否正确。我的直觉是,如果不需要,在结构体内部避免使用引用/生命周期。如果TestStructB拥有其成员,您的生活将更轻松:test_array: Vec<u8>, t_a: TestStructA - John Kugelman
我已经编辑了我的消息以执行一些追踪...希望它不会变成一个编辑混乱。TestStructA将是关于某个系统的某种配置。这个结构体的某个实例将存在于其他模块上,并且我想传递一个引用到一个2D渲染结构/模块,它将愉快地读取其值以进行不同的渲染可能性...无法确定这是否是“Rust式”处理事物的正确方式。 - Isaac
2
你需要在 TestStructB 上使用 Default 吗?你能把它移除吗? - John Kugelman
对此感到不确定...总的来说,我迄今使用的结构体,其默认特性使得它们的“普通”数据结构的初始化变得非常舒适...我可能会重构与这个特定结构相关的一个,以查看我是否真正需要它... - Isaac
1个回答

9
您的非常规要求是因为您正在尝试为包含引用的结构TestStructB实现Default。大多数情况下,Default用于具有所有拥有值或以某种方式为空的类型。
为了为引用&'a TestStructA实现Default,您必须拥有某种常量或泄漏值,该值是Default::default实现可以返回引用的类型 - 基本上需要存在一个&'static TestStructA。
如果向量为空,则很容易做到这一点,因为Vec::new()是const fn,因此我们可以构造TestStructA的完整编译时常量实例:
impl<'a> Default for &'a TestStructA {
    fn default() -> &'a TestStructA {
        static VALUE: TestStructA = TestStructA {
            byte_1: 10,
            byte_2: 20,
            vector: Vec::new()
        };
        &VALUE
    }
}

如果您想在向量中存储一些数据,则需要使用类似于 once_cell 的延迟初始化机制,以便执行 vec! 来分配向量的内容,然后返回对它的引用。
use once_cell::sync::Lazy;

static DEFAULT_A: Lazy<TestStructA> = Lazy::new(|| {
    TestStructA {byte_1: 10, byte_2: 20, vector: vec![1, 2, 3]}
});

impl<'a> Default for &'a TestStructA {
    fn default() -> &'a TestStructA {
        &DEFAULT_A
    }
}

如果您要这样做,我建议同时实现TestStructA的默认值(Default),因为仅为引用类型实现Default而不是拥有值类型会很奇怪。
impl Default for TestStructA {
    fn default() -> TestStructA {
        DEFAULT_A.clone()
    }
}

(这仅在 TestStructA 也实现了 Clone 的情况下才有效,但它可能应该这样做。如果不应该这样做,则将结构体文字放在 TestStructA::default 方法中,并使 DEFAULT_A 只被定义为 Lazy::new(TestStructA::default)。)
然而,所有这些细节只有在以下情况下才是必要的:
  1. 您正在为 TestStructB 实现 Default
  2. 它始终包含对 TestStructA 的引用。
您应该考虑在默认情况下,TestStructB 是否真正需要这个引用 - 如果 t_a 的类型为 Option<&'a TestStructA>,则例如,它可以默认为 None。我没有足够的信息来说这是否适合您的应用程序 - 根据这些结构的确切目的自行选择。

很遗憾,你的回复对我来说比我想象中更加晦涩难懂... 但是你击中了一个有趣的要害: “我的结构体是否需要作为引用存在于另一个结构体内部?” 凭直觉来说,我希望是这样的,但显然在Rust中,这会导致某些边角情况... 在我的程序中,这个结构体将包含关于屏幕的某种“全局配置”。 这个结构体的所有者将位于其他地方,而我只会发送引用供其他人使用... 我不想在这个其他结构体的所有函数中都传递这个结构体的引用,这对我来说感觉“低效”。 - Isaac
@Isaac 如果配置真的是全局的,那么你根本不需要在TestStructB中引用它--它通过全局性质已经可以在任何地方使用了。另一方面,如果它不是全局的,不同的TestStructB可能指向不同的TestStructA,那么你必须将&TestStructA传递到TestStructB的构造函数中,因此实现Default是不可能的(除非你可以像Kevin所观察到的那样将其包装在Option或其他东西中)。对于引用类型来说,实现Default并没有什么意义,这在合理的代码中并不是一个有意义的事情。 - trent
我谈到了“全局”而不是字面上的全局……我仍处于创建这个程序的阶段,一切都可以相对容易地进行重构。我不喜欢在程序中使用真正的全局对象,我尝试将数据存储在结构体中,然后在各处传递实例。但是我的问题似乎又变成了:理解结构体与类的区别(这可能是我的思维习惯)。从这些中我得到了一些好的话题:“结构体更好地持有拥有的数据”,否则事情会变得很陡峭。 - Isaac
我是说,目前我已经放弃了构建&Struct Default trait的原始意图,我的一些数据结构重构使我能够以不同的方式推进我的想法。正如我在问题评论中所说的那样,我从来没有想过这样一个复杂的任务,我只是一个可怜的Rust新手,躲避着被抛到我身上的神秘rustc错误,直到我到达了那个死胡同。时间会让我在处理Rust主题时做出更明智的决定 :) - Isaac

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接