切片和数组有啥区别?

Posted

技术标签:

【中文标题】切片和数组有啥区别?【英文标题】:What is the difference between a slice and an array?切片和数组有什么区别? 【发布时间】:2015-08-27 22:36:46 【问题描述】:

为什么在这个例子中&[u8]&[u8; 3] 都可以?

fn main() 
    let x: &[u8] = &[1u8, 2, 3];
    println!(":?", x);

    let y: &[u8; 3] = &[1u8, 2, 3];
    println!(":?", y);

&[T; n] 可以强制转换为&[T] 的事实是使它们可以容忍的方面。 — Chris Morgan

为什么&[T; n] 可以强制转换为&[T]?在什么其他条件下会发生这种强制?

【问题讨论】:

【参考方案1】:

[T; n] 是一个长度为n 的数组,表示为n 相邻的T 实例。

&[T; n] 纯粹是对该数组的引用,表示为指向数据的细指针。

[T] 是一个切片,一个无大小的类型;它只能通过某种形式的间接使用。

&[T],称为切片,是一个有大小的类型。这是一个胖指针,表示为指向第一项的指针和切片的长度。

因此,数组的长度在编译时是已知的,而切片长度是运行时的问题。数组目前在 Rust 中是二等公民,因为不可能形成数组泛型。 [T; 0][T; 1]&c. 的各种特征都有手动实现,通常最多 32 个;由于这个限制,切片更普遍有用。 &[T; n] 可以强制转换为 &[T] 的事实是使它们可以容忍的方面。

对于[T; 3],有一个fmt::Debug 的实现,其中T 实现Debug,另一个用于&T,其中T 实现fmt::Debug,以此类推u8 实现Debug&[u8; 3] 也可以。

为什么&[T; n] 可以强制转换为&[T]?在 Rust 中,强制何时发生?

它会在需要时强制执行,其他时候不会。我可以想到两种情况:

    如果某些东西需要&[T],而你给它一个&[T; n],它会默默地强制执行; 当您在[T; n] 上调用x.starts_with(…) 时,它会观察到[T; n] 上没有这样的方法,因此自动引用开始发挥作用并尝试&[T; n],这没有帮助,然后强制开始发挥作用,它会尝试 &[T],它有一个名为 starts_with 的方法。

sn-p [1, 2, 3].starts_with(&[1, 2]) 演示了两者。

【讨论】:

关于强制,我有点困惑。例如,Pattern trait 是为 &[char] 实现的。我创建了一个变量 x = [' '] 并尝试使用 &x 作为模式(例如,将其传递给字符串上的 trim_matches),但这会导致编译器错误。我可以看到 x 的类型是 &[char, 1]。如果我尝试传递 &x[..] 它会被强制进入 &[char] ,现在编译器会找到 Pattern 实现。有什么我想念的还是这个 &[T; n] -> &[T] 在寻找 trait 实现时没有检查强制转换? 我说的可能不完全正确,但这是我的理解:泛型实现有一个讨厌的习惯,会妨碍强制,因为它们本质上被列为比 array-to 更重要-切片强制。它看到所有满足FnMut(char) -> bool 的类型都实现了该特征,因此停在那里,试图满足该特征绑定而不是玩弄类型强制。使用 const 泛型,这可以通过手动提供 [char; N] 和/或 &[char; N] 实现 (impl<const N: usize>) 来解决。会不会是另外一回事。【参考方案2】:

为什么&[T; n] 可以强制转换为&[T]

另一个答案解释了为什么&[T; n] 应该强制转换为&[T],这里我将解释编译器如何计算出&[T; n] 可以强制转换为@ 987654330@.

有four possible coercions in Rust:

    传递性。

    如果T 强制转换为UU 强制转换为V,则T 强制转换为V

    指针弱化:

    移除可变性:&mut T&T*mut T*const T 转换为原始指针:&mut T*mut T&T*const T

    Deref trait:

    如果T: Deref<Target = U>,则&T 通过deref() 方法强制转换为&U (同样,如果T: DerefMut,则&mut T 通过deref_mut() 强制转换为&mut U

    Unsize trait:

    如果Ptr 是“指针类型”(例如&T*mut TBoxRc 等)和T: Unsize<U>,则Ptr<T> 强制转换为Ptr<U> .

    Unsize trait 自动实现:

    [T; n]: Unsize<[T]> T: Unsize<Trait>T: Trait struct Foo<…> …, field: T : Unsize< struct Foo<…> …, field: U >,前提是 T: Unsize<U>(以及一些使编译器的工作更容易的条件)

    (Rust 将 Ptr<X> 识别为“指针类型”,如果它实现了 CoerceUnsized。实际规则表述为,“如果 T: CoerceUnsized<U>T 强制转换为 U”。)

&[T; n] 强制转换为&[T] 的原因是规则 4:(a) 编译器为每个 [T; n] 生成实现 impl Unsize<[T]> for [T; n],并且 (b) 引用 &X 是指针类型。使用这些,&[T; n] 可以强制转换为&[T]

【讨论】:

【参考方案3】:

我根据 kennytm 和 Chris Morgan 的回答创作了这张照片。它描述了各种概念:

【讨论】:

n 在内存中不存在。对于切片,它在胖指针中,但对于数组或数组引用,它只存在于类型中,如N;都是编译时整理出来的,运行时根本不出现。

以上是关于切片和数组有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

大熊猫中的切片和副本有啥区别? [复制]

Golang 中数组(Array)和切片(Slice)的区别

golang基础 —— 切片和数组的区别

golang基础 —— 切片和数组的区别

go 切片长度与容量的区别

Go 语言数组和切片的区别