【问题标题】:I don't understand the difference between a slice and reference我不明白切片和参考之间的区别
【发布时间】:2020-07-23 20:05:17
【问题描述】:

我不明白切片和参考之间的区别。 &String&str 有什么区别?我在网上读到一些东西,说引用是一个细指针,而切片是一个胖指针,但我不知道,似乎也找不到这两个是什么意思。我知道切片可以强制转换为引用,但它是如何做到的呢? Deref 特征是什么?

【问题讨论】:

  • I read some stuff online stackoverflow.com/questions/57754901/…
  • 我读过,但我不明白为什么切片在编译时大小未知,例如 &str。
  • 给定一个类型为&str 的变量x - 它的大小是多少?将其与 i8(1 字节)或 i128(16 字节)或 struct Foo {x:i8,y:i128}(1 字节 + 16 字节 = 17 字节,忽略对齐)类型的变量进行比较。 &Foo 类型的变量将使用指针的大小,取消引用将为您提供大小为 17 字节的对象。由于str[i8] 的大小是可变的,因此必须将其存储为引用之外的胖指针。

标签: rust reference slice terminology


【解决方案1】:

在 Rust 中,切片是由不同长度的同质类型数据组成的连续块。

这是什么意思?

  • [u8] 是一个切片。在内存中,这是一个u8s 的块。切片本身就是数据。但很多时候,人们将&[u8] 称为切片。 &[u8] 是一个指向该数据块的指针。该指针包含两件事:指向数据本身的指针和数据的长度。由于它包含两个东西,因此它被称为胖指针。 &u8 也是一个引用(也可以被认为是一个指针在这种情况下 *),但我们已经知道它指向的都是一个u8。因此,它是一个细指针,因为它只有一个元素。

    您可以保证所有[u8] 中的数据属于u8 类型。

    由于您的[u8] 刚刚被定义为u8 类型的连续内存块,因此没有关于它有多大的编译时定义。因此,我们需要将它的长度存储在一个指向它的指针中。我们也不能把它放在堆栈上(这意味着:我们不能有一个只是 [u8] ** 的局部变量)。

扩展:

  • [T]Ts 的一部分。对于任何给定的T,只要T 本身是一个大小的类型***,我们就可以想象一个类型[T]
  • str 是字符串的一部分。它保证是有效的 UTF-8 文本,这就是将它与 [u8] 区分开来的一切。 Rust 可能已经抛弃了有效的 UTF-8 保证,只是将 str 中的所有其他内容定义为 [u8] 的一部分。

好吧,既然您不能在本地拥有切片****,您可能想知道我们如何创建切片。

答案是我们将数据放入已知大小的东西中,然后然后从中借用切片。

举个例子:

let my_array: [u32; 3] = [1, 2, 3];

我们可以像这样将my_array 分割成[u32]

let my_slice: [u32] = my_array[..];

但是由于我们不能拥有一个大小未知的局部变量,我们必须将它放在一个引用之下:

let my_slice: &[u32] = &my_array[..];

切片的意义在于,它是一种处理连续数据块的非常灵活(排除生命周期)的方法,无论数据来自何处。我可以很容易地将my_array 变成Vec<u8>,它是堆分配的,它仍然可以工作。

&String 和 &str 有什么区别?

&String 是对整个字符串对象的引用。 Rust 中的字符串对象本质上是一个Vec<u8>Vec 包含指向它“包含”的数据的指针,因此您的 &String 可以被认为是 &&str。而且,这就是我们可以执行以下任一操作的原因:

let my_string: String = "Abc".to_string();

let my_str: &str = &my_string[..]; // As explained previously
// OR
let my_str: &str = &*my_string;

对此的解释让我想到了你的最后一个问题:

什么是 deref 特征?

Deref 特征是描述取消引用 (*) 运算符的特征。正如你在上面看到的,我能够做到*my_string。这是因为String 实现了Deref,它允许您取消引用String。同样,我可以将Vec<T> 取消引用为[T]

但是请注意,Deref trait 不仅用于使用 * 的地方,还用于更多地方:

let my_string: String = "Abc".to_string();

let my_str: &str = &my_string;

如果我尝试将&T 类型的值分配给&U 类型的位置,那么Rust 将尝试取消引用我的T,次数与获得U 一样多,同时仍然至少保留一份参考资料。同样,如果我有一个&&&&....&&&&T,并且我尝试将它分配给&&&&....&&&&U,它仍然可以工作。

称为 deref coercion:自动将 &T 转换为 &U,其中一定数量的 *T 会导致 U


  • *:原始指针*const T*mut T 与引用大小相同,但编译器将其视为不透明。编译器不保证原始指针后面的内容,或者它们是否正确对齐。因此,取消引用它们是不安全的。但由于Deref trait 定义了一个安全的deref 方法,因此取消引用原始指针是特殊的,也不会自动完成。
  • **:这也包括其他动态大小的类型,例如特征对象和extern types。这也包括structs,它也包含一个动态大小的类型作为它们的最后一个成员,虽然这些很难正确构造,但在未来使用CoerceUnsized trait 会变得更容易。使用unsized_locals nightly 功能可以使所有这些(extern types 除外)无效,该功能允许使用动态大小的局部变量。
  • ***:大小类型是在编译时已知大小的所有类型。您可以笼统地识别它们;给定类型T,如果T: Sized,则T 的大小在编译时是已知的。如果T: ?Sized,那么它的大小在编译时可能是未知的(T: ?Sized 是调用者最灵活的要求,因为它接受任何东西)。由于切片要求内部数据是连续的,并且在大小和类型上是同质的,动态大小的类型(或!Sized)不可能包含在切片、数组或Vec<T> 中,并保持@ 987654394@ 索引。虽然 Rust 可能会编写特殊代码来索引一组动态大小的类型,但目前还没有。
  • ****:您实际上可以拥有一个切片,它只需要位于拥有它的指针下。例如,这可以是Box<[T]>Rc<[T]>。这些将自行取消分配切片(删除时为Box,删除Rc 的所有强引用和弱引用时为Rc(删除所有强引用时调用值的析构函数,但内存在所有弱引用都消失之前不会被释放。))。

【讨论】:

  • 引用book另一种没有所有权的数据类型是切片。切片让您可以引用集合中的连续元素序列,而不是整个集合。 所以[u8] 不是切片,它是一个未调整大小的数组; &[u8] 是一个切片。
  • 本书将&[T] 称为一个切片,因为它对于刚接触该语言的初学者来说更容易理解。 reference page for a slice 声明 切片类型写为 [T]the documentation for a slice 将其描述为 动态大小的视图到连续序列中,[T] .
【解决方案2】:

什么是参考

引用就像来自 C 的指针(表示内存位置),但引用永远不会无效*(即 null),并且您不能对引用进行指针运算。 Rust 的引用与 C++ 的引用非常相似。使用引用的一个重要动机是避免使用moveing 或cloneing 变量。假设您有一个计算向量和的函数(注意:这是一个玩具示例,获取向量和的正确方法是nums.iter().sum()

fn sum(nums: Vec<u32>) -> Option<u32> {
    if nums.len() == 0 {
        return None;
    }
    let mut sum = 0;
    for num in nums {
        sum += num;
    }
    Some(sum);
}

这个函数移动了向量,所以之后就不能用了。

let nums = vec!(1,2,3,4,5);
assert_eq!(sum(nums), 15);
assert_eq!(nums[0], 1); //<-- error, nums was moved when we calculated sum

解决方案是传递对向量的引用

fn sum(nums: &Vec<u32>) -> Option<u32> {
...
}
let nums = vec!(1,2,3,4,5);
assert_eq!(sum(&nums), 15);
assert_eq!(nums[0], 1); // <-- it works!

什么是切片

切片是“以指针和长度表示的 [连续] 内存块的视图”。它可以被认为是对数组(或类似数组的东西)的引用。 Rust 安全保证的一部分是确保您不会访问超出数组末尾的元素。为此,切片在内部表示为指针和长度。与不包含长度信息的指针相比,这很胖。与上面的 sum 示例类似,如果 nums 是一个数组,而不是 Vec,您将向 sum() 传递一个切片,而不是数组本身。

字符串与字符串

strutf-8 编码字符的数组,&amp;str 是 utf-8 编码字符的切片。 String 是 utf-8 编码字符的 VecString 实现 Deref&lt;Target=str&gt;,这意味着 &amp;String 的行为很像(强制)&amp;str。这类似于&amp;Vec&lt;u32&gt; 的行为方式类似于&amp;[u32](Vec 实现Deref&lt;Target=[T]&gt;


* 除非因不安全的生锈而无效

【讨论】:

  • P.S.需要明确的是,切片也是参考。当您对 T 类型具有泛型时,&amp;T肯定是一个引用,它可能也是一个切片。
猜你喜欢
  • 2010-09-23
  • 2023-03-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多