如何使特定结构体字段可变?

17

我有一个结构体:

pub struct Test {
    pub x: i32,
    pub y: i32,
}

我想要一个可以修改这个内容的函数 — 很容易:

pub fn mutateit(&mut self) {
    self.x += 1;
}

这意味着在 mutateit 函数调用期间,整个结构体都是可变的,对吗?我只想改变 x,不想改变y。有没有办法只可变地借用 x

2个回答

19

引用《Rust编程语言》:

Rust在语言层面上不支持字段可变性,因此你不能像这样写:

struct Point {
    mut x: i32, // This causes an error.
    y: i32,
}

你需要内部可变性,这在标准文档中有很好的描述:

use std::cell::Cell; 

pub struct Test {
    pub x: Cell<i32>,
    pub y: i32
}

fn main() {
    // note lack of mut:
    let test = Test {
        x: Cell::new(1), // interior mutability using Cell
        y: 0
    };

    test.x.set(2);
    assert_eq!(test.x.get(), 2);
}

如果您想将其纳入函数中:

impl Test {
    pub fn mutateit(&self) { // note: no mut again
        self.x.set(self.x.get() + 1);
    }
}

fn main() {
    let test = Test {
        x: Cell::new(1),
        y: 0
    };

    test.mutateit();
    assert_eq!(test.x.get(), 2);
}

3
虽然这个方法可行,但需要注意的是 std::cell::Cell 实现了 !Sync,这意味着该结构体不能再在线程之间传递。因此,虽然内部可变性允许通过不可变引用来改变某些字段的值,但它也改变了语义并将 Test 限制为严格单线程。 - Ted Klein Bergman

0
你不能直接做你所要求的事情,但是当你处于需要这样做的情况时,也许你的结构体中的不可变部分应该是引用而不是拥有的值。由于它们将是不可变借用,它们的内容是受保护的。这通常在逻辑上符合情况——保护这些字段必须有原因,可能是因为它们并不真正属于你,不能随意操作。
将你的示例更改为使用String,以确保没有任何隐式复制发生,考虑以下情况:
#[derive(Debug)]
pub struct Test<'a> {
    pub x: String,
    pub y: &'a String,
}

impl<'a> Test<'a> {
    pub fn mutateit(&mut self) {
        self.x.insert(0, '#');
    }
}

fn main() {
    let mut foo = Test{x: "Hello".to_string(), y: &"World".to_string()};
    // foo.y.insert(0, '+');  // This won't work!  I can't mutate y because it is borrowed inside Test
    
    let yref = foo.y;  // If y wasn't behind a & this would cause a partial move

    println!("{:?}", foo);
    println!("{:?}", yref);    
    
    foo.mutateit();    // The borrow into yref would block this mutate, except its a reference not a borrow.
    
    println!("{:?}", foo);
    println!("{:?}", yref);  // So my borrow lifetime is fine.
}

这个解决方案将迫使你面对一个问题,即谁最终应该拥有 y,但你内心深处可能已经知道答案,如果你觉得有必要保护它的话。

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