【问题标题】:Why are "&str" and "&String" treated as references to the same value?为什么 \"&str\" 和 \"&String\" 被视为对相同值的引用?
【发布时间】:2022-09-26 14:21:30
【问题描述】:

生锈的初学者。看了4.3章后,我对4.3章的内容感到困惑,它与原理有交叉引用

在任何给定时间,您都可以拥有一个可变引用或任意数量的不可变引用。

简化的例子是

fn main() {
    let mut str: String = String::from(\"hello\");
    let slice: &str = &str[0..2]; // #1
    str.clear(); // #2
    println!(\"{}\", slice);
}

此示例在编译时会导致恐慌错误。教程注释说原因是它违反了上面的原则。但是,我无法理解。在我看来,#1 创建了一个类型为不可变的引用&str, 相反,#2 使用类型创建可变引用&细绳,根据类型,它们似乎不是指相同的东西,因为它们具有不同的引用类型。为什么它违反了上面似乎只适用于相同类型的引用的原则?有什么原则可以澄清这个问题吗?

  • &str 是一个切片,它可以是指向 String 内部区域的指针。它们共享内存,因此您无法修改 String
  • @mousetail 有没有这样明确解释的原则?也就是说,不能同时存在可变引用和不可变引用的确切含义是什么?
  • @ChayimFriedman 我认为它们是不同的问题。在这个问题中,我尝试询问两种不同的引用类型是否会违反问题中引用的原则。
  • \"为什么它违反了上面似乎只适用于相同类型的引用的原则?\"- 无论引用的类型如何,借用规则都适用,仅适用于它来自事务的地方。您可以从Person 借用名称&str,并且在您借用它时,您不能修改该人。

标签: rust


【解决方案1】:

我想你误会了。

String不是str 的可变版本。这是它自己的类型。

let mut x: Stringlet x: String 的可变版本。

String拥有并且可以修改。 str 是“切片”类型,它指的是字符串的内容,可以在String 内部,也可以在全局内存中以&'static str 的形式出现。

没有mut str,因为str 根据定义是对字符串不可变部分的引用。


让我们看看你的代码。 (将str 重命名为s,因为这太混乱了)

fn main() {
    // Your variable `s` is `mut String`. It is a mutable string.
    let mut s: String = String::from("hello");
    
    // Your variable `slice` is a `&str`.
    // It isn't mutable, it is a reference to a substring of `s`.
    let slice: &str = &s[0..2]; // #1
    
    // Here we already hold an immutable reference to `s` through the `slice` variable.
    // This prevents us from modifying `s`, because you cannot reference an object mutably while
    // it is borrowed immutably.
    s.clear(); // #2

    // This line is only important to force the variable `slice` to exist.
    // Otherwise the compiler would be allowed to drop it before the `s.clear()` call,
    // and everything would compile fine.
    println!("{}", slice);
}

那里没有&String。通过&s[0..2] 获取String 的一部分会自动创建&str,因为这就是specification of String says

fn index(&self, index: Range) -> &str


为什么它违反了上面似乎只适用于相同类型的引用的原则?

这是不正确的。它们不必是同一类型。如果您持有一个引用了String 内容的&str,那么当&str 引用存在时,String 对象也会被阻止发生突变。您甚至可以将引用存储在其他对象中,然后这些对象的存在仍然会阻止原始String


它们绝对是不同的对象

这并不意味着它们无法连接。

为了证明两个不同类型的对象可以具有连接的生命周期,请查看以下代码:

#[derive(Debug)]
struct A {
    pub value: u32,
}

#[derive(Debug)]
struct B<'a> {
    pub reference: &'a u32,
}

impl A {
    pub fn new(value: u32) -> Self {
        Self { value }
    }

    pub fn set(&mut self, value: u32) {
        self.value = value;
    }
}

impl<'a> B<'a> {
    pub fn new(a: &'a A) -> Self {
        Self {
            reference: &a.value,
        }
    }
}

fn main() {
    let mut a = A::new(69);
    println!("a: {:?}", a);

    // Can be modified
    a.set(42);
    println!("a: {:?}", a);

    // Create a B object that references the content of `a`
    let b = B::new(&a);
    println!("b: {:?}", b);

    // While `b exists, it borrows a part of `a` (indicated through the fact that it has a lifetime type attached)
    // That means, while `b` exists, `a` cannot be modified
    a.set(420); // FAILS

    // This ensures that `b` actually still exists
    println!("b: {:?}", b);
}

错误信息很清楚:

error[E0502]: cannot borrow `a` as mutable because it is also borrowed as immutable
  --> src/main.rs:43:5
   |
38 |     let b = B::new(&a);
   |                    -- immutable borrow occurs here
...
43 |     a.set(420); // FAILS
   |     ^^^^^^^^^^ mutable borrow occurs here
...
46 |     println!("b: {:?}", b);
   |                         - immutable borrow later used here

请注意,B 类型附加了一个生命周期 'a。此生命周期将由编译器在实例化时自动派生,并用于防止在 B 存在时对引用的 A 对象进行可变使用。

&amp;str 还附加了一个生命周期,用于防止对引用的 String 对象进行可变访问。


我希望这能澄清一些事情。如果您还有其他问题,请随时提问。

【讨论】:

  • 感谢您的回答。我有点无法理解:“如果你持有一个引用字符串内容的 &str,那么当 &str 引用存在时,字符串对象也会被阻止发生变异。”在我看来,从c++的角度来看,slice是对在堆中创建的对象的引用,而用于调用方法clear的隐式创建的&amp;s是对与s关联的对象的引用,为什么他们会认为违反了这样的原则:“你不能可变地引用一个对象,而借用它是不可变的。”?它们绝对是不同的对象。
  • “它们绝对是不同的对象”- Rust 中的对象可以包含对其他对象(甚至是不同类型)的引用。如果是这样,那么它们的生命周期是连接的,并且引用的原始对象通过借用检查器被锁定。请注意,这是一个编译时检查,在运行时实际上没有锁定任何内容。
  • slice 不会复制它的数据,它只是引用来自s 的数据。这就是它们连接在一起的原因。
  • @xmh0511 添加了另一个代码示例,以阐明对象不必是相同类型的生命周期才能工作。
  • "slice 是对堆中创建的对象的引用“ - 这是真的,但不会让问题消失。这是一个问题所有权. “在堆上创建的对象”没有引用计数,因此必须有人拥有它。拥有它的对象是s,这就是为什么只要slice 存在,s 就会被阻止。我的意思是,从逻辑的角度来看:如果s.clear()成功,那么slice 的内容就会改变。 slice 是不可变引用这一事实意味着它将不会slice 存在时更改。
猜你喜欢
  • 2013-02-02
  • 1970-01-01
  • 2021-11-07
  • 2012-08-05
  • 2017-11-21
  • 2019-09-22
  • 1970-01-01
  • 2019-12-23
  • 2019-12-12
相关资源
最近更新 更多