当前位置: 首页 > 知识库问答 >
问题:

为什么允许借用结构成员

尹昀
2023-03-14

如果我有一个封装两个成员的结构,并基于另一个成员更新其中一个,那么只要我这样做就可以了:

struct A {
    value: i64
}

impl A {
    pub fn new() -> Self {
        A { value: 0 }
    }
    pub fn do_something(&mut self, other: &B) {
        self.value += other.value;
    }
    pub fn value(&self) -> i64 {
        self.value
    }
}

struct B {
    pub value: i64
}

struct State {
    a: A,
    b: B
}

impl State {
    pub fn new() -> Self {
        State {
            a: A::new(),
            b: B { value: 1 }
        }
    }
    pub fn do_stuff(&mut self) -> i64 {
        self.a.do_something(&self.b);
        self.a.value()
    }
    pub fn get_b(&self) -> &B {
        &self.b
    }
}

fn main() {
    let mut state = State::new();
    println!("{}", state.do_stuff());
}

也就是说,当我直接提到self时。b 。但当我将do\u stuff()更改为以下内容时:

pub fn do_stuff(&mut self) -> i64 {
    self.a.do_something(self.get_b());
    self.a.value()
}

编译器抱怨:<代码>无法将`*self`借用为不可变,因为` self `。a `也被借用为可变的。

如果我需要执行比返回成员更复杂的操作来获取a.do\u something()的参数,该怎么办?我必须创建一个按值返回b的函数并将其存储在绑定中,然后将该绑定传递给do\u something()?如果b很复杂怎么办?

更重要的是,对我的理解,什么样的内存不安全是编译器从这里拯救我?

共有2个答案

司徒运锋
2023-03-14

是的,编译器隔离函数是为了进行安全检查。如果没有,那么每个函数本质上都必须到处内联。没有人会欣赏这一点,至少有两个原因:

  1. 编译时间将飞速增长,许多并行化的机会将不得不放弃。
  2. 对函数N次调用的更改可能会影响当前函数。另请参阅为什么Rust中需要显式生命周期?这涉及相同的概念。

编译器从这里拯救了我什么样的内存不安全

没有,真的。事实上,正如你的例子所示,这可能会导致误报。

这对保持程序员的理智更有好处。

当我遇到这个问题时,我给出并遵循的一般建议是编译器正在引导您在现有代码中发现一种新类型。

您的特定示例有点过于简化,因此没有意义,但如果您有struct Foo(A, B, C)并发现Foo上的方法需要AB,这通常是一个很好的迹象,表明有一个由AB组成的隐藏类型:struct Foo(Bar, C); struct Bar(A, B)

这不是灵丹妙药,因为您最终可以获得需要每对数据的方法,但根据我的经验,它在大部分时间都有效。

孟洋
2023-03-14

可变引用的一个关键方面是,当它们存在时,保证它们是访问特定值的唯一方法(除非它们被重新加载,这会暂时“禁用”它们)。

当你写的时候

self.a.do_something(&self.b);

编译器能够看到借来的代码是自我的。一个(隐式地执行方法调用)与借来的self不同。b,因为它可以对直接字段访问进行推理。

但是,当你写作的时候

self.a.do_something(self.get_b());

然后编译器在self上看不到借用。b,而是借用自我。这是因为方法签名上的生存期参数无法传播有关借用的详细信息。因此,编译器无法保证self返回的值。get\u b()不允许您访问self。a,它将创建两个可以访问self的引用。a,其中一个是可变的,这是非法的。

字段借用不跨函数传播的原因是为了简化类型检查和借用检查(对于机器和人类)。原则是签名应该足以执行这些任务:更改函数的实现不应导致其调用者出错。

如果我需要做一些比返回成员更复杂的事情来获取a.do_something()的参数怎么办?

我会将get_bState移动到B并在self. b上调用get_b。这样,编译器可以看到self. aself. b上的不同借用,并将接受代码。

self.a.do_something(self.b.get_b());
 类似资料:
  • 当我编写以下代码时,我得到了预期的错误

  • 问题内容: 除了JSONP,为什么要遵循相同的域策略? 问题答案: 出于安全原因,已实施“同源起源策略”;引用维基百科的相关句子: 这种机制对现代Web应用程序具有特殊的意义,因为Web服务器广泛依赖于HTTP cookie来维护经过身份验证的用户会话,因为服务器基于HTTP cookie信息进行操作以揭示敏感信息或执行状态更改操作。 必须在客户端维护不相关站点提供的内容之间的严格分隔,以防止丢失

  • 为什么Java不允许在接口中使用私有成员?有什么特别的原因吗?

  • 我知道借阅检查器不允许多个可变借阅。例如,下面的代码无效: 但是,如果第一次借款因超出范围而被放弃,第二次借款是有效的: 由于非词汇生存期(NLL),第一次借用甚至不必超出范围-借用检查器只要求不再使用它。因此,以下代码在2018年有效: 但我不明白为什么下面的代码无效: 编译错误信息: 从错误消息中,我认为NLL可能还不支持这种情况。所以,我提前删除了: (铁锈操场) 但我得到了一个更令人困惑的

  • 我正在阅读jls§5.1.7,它说有9种拳击类型,第9种是拳击 然后我读到的拆箱转换会抛出一个。好的,这很明显。那么为什么的装箱不会抛出一个以及装箱值有什么用呢?

  • 如果我有一些琐碎的东西,比如(为了澄清,我并不是说这是一个好的实现,只是一个演示成员函数部分模板专门化失败的示例): 我无法通过执行以下操作来专门化每个功能: 不幸的是,C标准不允许: 14.5.5.31、类模板局部特化成员的模板参数列表应与类模板局部特化的模板参数列表匹配类模板局部特化成员的模板参数列表应与类模板局部特化的模板参数列表匹配。 因此,唯一的解决方案(据我所知)是使用类型特征或用样板