rust 为什么“&str”和“&String”被视为对同一个值的引用?

vmpqdwk3  于 9个月前  发布在  其他
关注(0)|答案(2)|浏览(114)

刚开始生 rust 的人。在我阅读第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);
}

此示例在编译时会导致错误。

error[E0502]: cannot borrow `str` as mutable because it is also borrowed as immutable
 --> src/main.rs:4:5
  |
3 |     let slice: &str = &str[0..2]; // #1
  |                        --- immutable borrow occurs here
4 |     str.clear(); // #2
  |     ^^^^^^^^^^^ mutable borrow occurs here
5 |     println!("{}", slice);
  |                    ----- immutable borrow later used here

教程注解说原因是它违反了上面的原则。但是,我不能理解。在我看来,#1创建了一个类型为**&str的不可变引用,相反,#2创建了一个类型为&String**的可变引用,根据类型,它们似乎引用的东西不一样,因为它们有不同的引用类型。为什么它违反了上面的原则,似乎只适用于相同类型的引用?有没有什么原则可以澄清这个问题?

u4vypkhs

u4vypkhs1#

我想你误会了。
String**不是str的可变版本。这是它自己的类型。
let mut x: Stringlet x: String的可变版本。
Stringowned,可以修改。str是一个“slice”类型,指的是一个字符串的内容,或者在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,因为String的规范是这样说的:
fn index(&self,index:Range)-> &str
为什么它违反了上面的原则,似乎只适用于相同类型的引用?
这是不正确的。它们不一定是同一类型。如果你持有一个&str,它引用了一个String的内容,那么当&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对象的可变使用。
&str还附加了一个生存期,用于防止对引用的String对象的可变访问。

kiz8lqtg

kiz8lqtg2#

你可以把Rust中的String看作是包含三个数据块--一个指针(指向[堆上]的一个分配的内存块,它包含一个连续的字节序列--本质上是一个堆分配的u8数组),一个整数,它存储上述内存块的容量(也就是内存块的大小)。缓冲区的大小),以及存储字符串大小的整数(即,实际上使用了多少缓冲器)。
当您从String创建切片(&str对象)时,切片仍将指向String对象保存的数据。对于所有意图和目的,组成切片的数据是一个const(使用C语言的说法)指针和一个指示切片立即大小的整数(它没有提供有关底层缓冲区大小的信息)。在最初的文章中,... slice变量引用str保存的数据(作为不可变的借用)。
如果你再看看String对象的clear方法的签名行.

pub fn clear(&mut self)

您可以看到,对clear的方法调用涉及到调用对象的不可变引用。因此,一旦调用了clear方法,通过slice对数据的任何访问都将消失。方法调用中的可变引用导致了一个可变借用。slice变量不再借用数据。这就是为什么Rust编译器会抛出错误的原因,这是println!调用的结果。
即使在C或C++中,你的程序也是一个糟糕的举动,因为你试图访问你刚刚清除的数据。这可能类似于使用悬空指针访问释放的内存。这些都是Rust的数据所有权/数据借用模型试图防止的许多类型的内存错误。

fn main() {
    let mut str: String = String::from("hello");
    let slice: &str = &str[0..2];
    println!("{}", slice); // prints he
    str.clear(); // slice is of no use after this line!
    str.insert_str(0,"world");
    println!("{}", str); // prints world
}

上面的代码编译和运行没有错误。但是,重要的是要认识到,slice只通过对println!的第一次调用有效地借用数据。在此之后,由于调用clear,会有一个临时的可变借用,然后所有权返回给str。
重要的是要记住,你可以有尽可能多的不可变引用到一个对象,只要你喜欢。然而,一旦您有了一个可变借用(一个可变引用),那么您所有的不可变借用都将被没收(您不能再次使用它们)。
当然,没有什么能阻止你创造新的借款!

fn main() {
    let mut str: String = String::from("hello");
    let slice: &str = &str[0..2];
    println!("{}", slice); // prints he
    str.clear(); // slice is of no use after this line!
    str.insert_str(0,"world");
    println!("{}", str); // prints world
    let slice: &str = &str[0..2]; // new immutable borrow!
    println!("{}", slice); // prints wo
}

因此,正如您所看到的,这激发了关于引用的生命周期(借用的生命周期)的整个讨论。因为,正如你所观察到的,它们并不是无限期地活着。
为了解决这个问题,为什么&str&String被视为对相同值的引用-答案是它们不是。它们都可以保存指向同一个数据数组的指针(即,它们可以具有共同的数据成员,即指针)。但是,原则上,其其余数据是独立的。
此外,您可以在本地定义&str变量,这些变量被赋值为原始字符串文字。这些变量将完全存在于堆栈中。它们提供了一种使用方便的不可变数据完成许多常见字符串任务的方法-而不必使用任何String对象机制。但是,每当您希望数据在堆栈之外持久化,或者希望能够改变数据时,您就进入了String对象特别有用的领域。
总之,&str对象作为不可变的轻量级对象非常有用。此外,由于它们是轻量级和灵活的,它们也是处理对String对象的不可变引用的好方法。

相关问题