【问题标题】:Why is the mutability of a variable not reflected in its type signature in Rust?为什么变量的可变性没有反映在 Rust 的类型签名中?
【发布时间】:2020-01-13 10:16:34
【问题描述】:

据我了解,可变性并未反映在变量类型签名中。例如,这两个引用具有相同的类型签名&i32

let ref_foo : &i32 = &foo;
let mut ref_bar : &i32 = &bar;

为什么会这样?这似乎是一个相当大的疏忽。我的意思是,即使是 C/C++ 也更明确地使用两个 const 来表示我们有一个指向 const 数据的 const 指针:

const int * const ptr_foo = &foo;
const int * ptr_bar = &bar;

有没有更好的思考方式?

【问题讨论】:

  • 不是mut 表示可变性吗?
  • c++ 也没有const int& const 是不是你错过了什么?
  • 详细说明最后一条评论:您将橙子与苹果进行比较。 rust 的例子是关于引用的(C++ 中没有两个consts 用于引用),而 C++ 的例子是关于指针
  • 在 C++ 中,引用是固定的,不能重新分配它们以引用其他内容。因此与&运算符关联的const只是指它绑定的数据是否为const

标签: pointers rust reference


【解决方案1】:

可变性是 Rust 中 binding 的属性,而不是 type 的属性。

一个值的唯一所有者总是可以通过将它移动到一个可变绑定来改变它:

let s = "Hi".to_owned();  // Create an owned value.
s.push('!');              // Error because s is immutable.
let mut t = s;            // Move owned value to mutable binding.
t.push('!');              // Now we can modify the string.

这表明可变性不是值类型的属性,而是它的绑定。当然,该代码仅在当前未借用该值时才有效,这将阻止移动该值。共享借用仍然保证是不可变的。

引用的可变性与绑定的可变性正交。 Rust 使用相同的 mut 关键字来区分这两种类型的引用,但它是一个单独的概念。

interior mutability 模式再次与上述模式正交,因为它类型的一部分。包含CellRefCell 或类似名称的类型即使只持有对它们的共享引用也可以修改。

在完成对值的更改后,将值重新绑定为不可变的常见模式:

let mut x = ...;
// modify x ...
let x = x;

Rust 中的所有权语义和类型系统与 C++ 有点不同,我更喜欢 Rust 方式。正如你所暗示的,我不认为它天生就缺乏表现力。

【讨论】:

  • “引用的可变性与绑定的可变性是正交的。”这确实需要在书中更清楚地强调。
  • 我想在这个答案中添加的一件事是简明扼要地说明这一行中每个mut 的含义:let mut ref_bar : &mut i32 = &mut bar;。开始/例如:ref_bar 是一个对可变 i32 可变的引用,它(相互)专门绑定到名为 bari32
  • @allsey87 第一个mut(从左到右阅读)是绑定的一部分,如上所示。第二个 mut 属于 &mut,这意味着 ref_bar 的类型是对某些数据(在本例中为 i32)的某种唯一引用(或可变引用,但是你想查看它)。最后一个mut 再次属于&mut,但这次它是一个运算符,意思是“获取唯一引用”(或可变引用)。 &mut operator 创建 &mut 值,& operator 创建 & 值。
【解决方案2】:

C++ 和 Rust 中的常量根本不同。在 C++ 中,常量是任何类型的属性,而在 Rust 中,它是引用的属性。因此,在 Rust 中没有真正的常量类型。

以这段 C++ 代码为例:

void test() {
    const std::string x;
    const std::string *p = &x;
    const std::string &r = x;
}

变量x 被声明为常量类型,因此对它创建的任何引用也将是常量,并且任何修改它的尝试(例如const_cast)都将呈现未定义的行为 .注意const 是对象类型的一部分。

然而,在 Rust 中,没有办法声明一个常量变量:

fn test() {
    let x = String::new();
    let r = &x;

    let mut x = x; //moved, not copied, now it is mutable!
    let r = &mut x;
}

这里,const-ness 或 mut-ness 不是变量类型的一部分,而是每个引用的属性。甚至变量的原始名称也可以视为参考。

因为当你在 C++ 或 Rust 中声明一个局部变量时,你实际上是在做两件事:

  • 创建对象本身。
  • 声明访问对象的名称,引用之类的。

当您编写 C++ 常量时,您将同时创建常量、对象和引用。但是在 Rust 中没有常量对象,所以只有引用是常量。如果您移动对象,您会处置原始名称并绑定到新名称,这可能是可变的,也可能不是可变的。

请注意,在 C++ 中,您不能移动常量对象,它将永远保持不变。

关于指针有两个consts,如果你有两个间接,它们在Rust中是一样的:

fn test() {
    let mut x = String::new();
    let p: &mut String = &mut x;
    let p2: &&mut String = &p;
}

关于什么更好,这是一个品味问题,但请记住常量在 C++ 中可以做的所有奇怪的事情:

  • 常量对象始终是常量,除非它不是:构造函数和析构函数。
  • 具有可变成员的常量类并不是真正的常量。 mutable 不是类型系统的一部分,而 Rust 的 Cell/RefCell 是。
  • 具有常量成员的类很难使用:默认构造函数和复制/移动运算符不起作用。

【讨论】:

  • 说的都对,没问题,但它是如何回答标题中的问题的呢?为什么ref_fooref_bar 似乎都具有&i32 类型,即使只有后者可以用作可变类型?
  • @BartekBanachewicz:这是因为在 C++ 中 const 是类型的一部分(const int & 是对 const int 的引用),而在 Rust 中它是引用的属性,@ 987654336@ 是对i32 的常量引用。
  • “但在”究竟是什么?
  • @Aiueiia:你是指段落末尾的“但在”吗?我认为这是一个复制/粘贴错字,现在修复它。
【解决方案3】:

在 C++ 中,默认情况下一切都是可变的,const 关键字表示您想要更改该行为。

在 Rust 中,默认情况下一切都是im可变的,mut 关键字表示您想要更改该行为。

请注意,对于指针,Rust 确实需要 mutconst 关键字:

let ref_foo : *const i32 = &foo;
let mut ref_bar : *const i32 = &bar;

因此,您的示例是等价的,但 Rust 不那么冗长,因为它默认为不可变的。

即使是 C/C++ 也做得更好

多年的 C++ 和 Rust 开发经验让我相信,Rust 处理可变性的方式(例如,默认为不可变,但还有其他区别)要好得多。

【讨论】:

  • 这并没有回答为什么 ref_bar 被声明为 mut 的事实似乎没有被观察到。
  • 没有观察到是什么意思?
  • 编译器将这两个都列为&i32,尽管它只允许修改后者。
  • 可能反映(在类型签名中)是比观察到的更好的词/短语
  • @BartekBanachewicz 不同之处在于&i32 的所有者之一是可变的(所有者持有的值可能会改变),并且不允许修改后者请参阅:play.rust-lang.org/…跨度>
猜你喜欢
  • 2018-08-19
  • 2016-10-19
  • 2019-11-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多