rust 当将对象修改为结构中的字段时,为什么在方法中使用&mut self

webghufk  于 2023-03-23  发布在  其他
关注(0)|答案(2)|浏览(78)

我仍然很难理解为什么要用&mut self来修改我的结构体所拥有的对象的内部状态。我明白为什么我必须至少使用&self,因为我不想消耗self,结束它的存在。我也明白为什么如果我要修改我的结构体的字段,我必须使用&mut self,但我没有。
我有以下结构和实现:

struct Writer {
    obj: json::JsonValue
}

impl<'b> Writer {
    fn from(obj: json::JsonValue) -> Self {
        Self {
            obj
        }
    }

    fn put(&mut self, field_name: &str, value: bool) {
        self.obj.insert(field_name, value);
        //   ^^^- not modifying this, but a field inside "obj"
    }

    fn release_ownership(self) -> json::JsonValue {
        self.obj
    }
}

用法:

writer.put(field_name, true);

使用&self而不是&mut self,我会得到一个编译器错误:

`^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable`

Rust的书教会了我:
如果我们想改变我们调用方法的示例,作为方法的一部分,我们会使用&mut self作为第一个参数。
我没有看到我是如何修改结构体的,我只是修改了结构体 * 中的一个字段(这里是obj)*,而不是结构体本身。
JsonValue::insert()的定义中,我可以看到&mut self,我也不理解它,因为它“只”通过JsonValue::Object::insert()改变object的 * 内容 *。
Rust book表示:
注意,整个示例必须是可变的;Rust不允许我们只将某些字段标记为可变。
如果我想修改字段值,我可以理解这一点,但如果字段是指向其他被修改的结构的指针,我就不能理解了。
我的直觉告诉我,我用&mut限定不是指对数据的直接引用,而是指沿着所有指针的整个路径或最终引用。如果是这种情况:为甚么呢?
然而,这似乎是多余的,因为JsonValue::insert()已经强制了对其内部对象的可变访问。因此编译器知道,JsonValue的示例被可变地借用用于插入,并且不能再次借用,直到它被释放。
家庭作业:阅读这本书,做我的search

pgvzfuti

pgvzfuti1#

访问链沿着的所有内容都必须声明为可变引用,以确保您不能获得对同一对象的两个可变引用。请考虑以下情况:

struct ContainsMutable<'a>(&'a mut i8);

fn main() {
    let mut a = 99;
    let cm = ContainsMutable(&mut a);
    let cmref1 = &cm;
    let cmref2 = &cm;
    // now if both references allowed mutable access to the pointee
    // `a` we'd have a problem because this would be allowed
    std::thread::scope(|s| {
        s.spawn(|| {
            *cmref1.0 = 55;
        });
        s.spawn(|| {
            *cmref2.0 = 31;
        });
    });
}

因此,我们永远不能被允许对共享引用后面的东西进行可变访问(我们通过路径沿着某处的共享引用访问的东西),因为我们可以轻松地创建对同一对象的可变引用。
然而,这似乎是多余的,因为JsonValue::insert()已经强制了对其内部对象的可变访问。因此编译器知道,JsonValue的示例是可变地借用的,直到它被释放。
假设编译器可以在编译时预测函数何时被调用并禁止它,但它不能,或者借用检查是在运行时完成的,它不是,借用检查是在编译时静态完成的。你可以使用RefCellMutexRwLock来进行运行时借用检查。

wa7juj8i

wa7juj8i2#

我想困扰你的问题是关于常量/可变性的浅本质和深本质。根据编程语言的不同,这方面可以有不同的看法。如果你有一个语言背景,在那里(几乎)所有内容都通过引用访问(Java,JS,Python...),用引用其他内容的成员定义结构可以被认为好像该成员的内部细节不是结构的一部分(它们在结构之外)。因此,在这个成员的内部细节中改变一些东西可以被看作是而不是改变结构。
在C语言中,这就是众所周知的浅常数:

typedef struct {
    int a;
    int b[3];
} S1;
typedef struct {
    int a;
    int *b;
} S2;
// ...
const S1 s1 = { 11, { 99, 88, 77 } };
// s1.a = 22; // error, a is altered but s1 is const
// s1.b[0] = 33; // error, b is altered but s1 is const 
const S2 s2 = { 11, malloc( ... ) };
// s2.a = 22; // error, a is altered but s2 is const
// s2.b = realloc(s2.b, ... ); // error, b is altered but s2 is const
s2.b[0] = 33; // !!! allowed !!! since b is not altered itself (but what it refers to)

在我看来,这是非常混乱的。
在C++中,标准容器更喜欢深度一致性:

struct S {
    int a;
    std::vector<int> b;
};
// ...
const S s{ 11, std::vector{99, 88, 77} };
// s.a = 22; // error, a is altered but s is const
// s.b[0] = 33; // error, altering the stored values is an alteration of the member itself

在我看来,这是更加一致和容易推理:“如果我改变了我的结构的任何部分,我的结构就作为一个整体被改变了”。
在Rust中,应用了深度恒定性/可变性(与C++标准容器一样)。

struct S1 {
    a: u8,
    b: [u8; 3],
}
struct S2 {
    a: u8,
    b: Box<[u8; 3]>, // we could have used Vec instead
}
let s1 = S1 { a: 11, b: [99, 88, 77] };
// s1.a = 22; // error, a is altered but s1 is not mut
// s1.b[0] = 33; // error, b is altered but s1 is not mut 
let s2 = S2 { a: 11, b: Box::new([99, 88, 77]) };
// s2.a = 22; // error, a is altered but s2 is not mut
// s2.b[0] = 33; // error, b is altered but s2 is not mut

对于S1,很明显数组直接发生在结构内部,但是如果我们改变了存储的内部细节,就像在S2中一样,我们可能不想改变语义。最后一个例子表明,关于我们存储b数据的方式(数组,boxed-array,vector...)的实现细节并没有改变语义:“如果我改变了我的结构的任何部分,我的结构就作为一个整体被改变了”。
回到最初的例子,JsonValueWriter结构的一部分,那么改变这一部分应该被认为是整个结构的改变。默认情况下,这是更安全的选择。另一方面,如果您有充分的理由使用非常广泛的语义(à la Java,JS,Python...)好像所有权没有明确定义但最终在几个容器之间共享,或者好像任何人都可以随时修改任何一段数据在我看来,最好从一个非常严格和安全的语义开始,如果必要的话,显式地选择加入一些不那么严格的语义(它是明确的,任何人都可以在源代码中看到它)而不是相反(语言允许你做任何事情,但你希望没有人会打破你所想到的严格语义)。

相关问题