rust智能指针使用总结
admin
撰写于 2024年 05月 02 日

Box

特性:

  1. 在堆上分配内存去存储T,并返回一个指向该内存的指针
  2. 单一所有权(只能有一个 Box 指向堆上的数据,拥有T的所有权,T不再拥有所有权),只有这个 Box 对象才能访问和修改存储在堆上的数据,避免了数据竞争和并发问题
  3. 离开作用域之后自动释放T内存,或者手动调用drop()方法释放
  4. 在编译时已知大小(它是一个指向堆上分配的值的指针),但可以存储未知大小的数据(存储的是指向堆上数据的指针,而不是数据本身)

应用场景:

  1. 递归数据结构,Box是一个非常好的选择。例如,你可以使用Box来创建一个链表或者树形结构。
  2. 动态大小类型 当你需要在堆上分配一些动态大小的类型(比如trait对象)时,Box很有用
  3. 处理大型数据 ,使用Box<T>可以提供一些额外的性能优势

为什么使用:

  1. 内存管理:Rust的所有权模型要求在编译时知道所有变量的大小,但有时候我们可能需要在运行时动态地分配内存。Box提供了一种在堆上分配内存的简单方式,并且当Box离开作用域时,它会自动清理其占用的内存。
  2. 抽象:Box可以用来创建trait对象,这是一种在运行时进行多态的方式。这允许我们编写能够处理不同类型的代码,而这些类型都实现了一个共同的trait。

Cell

Cell<T> 只能用于 Copy 类型,基本的数字类型、布尔类型、字符类型等都实现了 Copy trait

原理:

get操作的时候:获取 Cell 内部的 T 类型的值的位复制,返回这个复制的值

set操作的时候:接受一个 T 类型的值作为参数,使用这个值替换 Cell 内部的当前值

为什么要求一定实现 Copy trait?

如果不这样约定,

  1. Cell的get方法就会返回T的引用,就不能随便对其进行修改操作了,因为rust对引用有要求只能存在一个可变引用和多个不可变引用,我们多次调用set或者get的时候实际上获取了多个可变引用或者多个不可变引用,违背了rust的借用规则
  2. 在set的时候,会对T值进行修改,会导致之前get的值发生了改变,因为是同一个引用

如果使用Copy trait,上面的问题都不会出现

  1. get方法返回的是T数据的副本,并不会影响原来值的所有权
  2. set的时候是对原来的值进行替换修改,也不会影响原来值的所有权

get 和set操作实际都不是真正对原始值进行操作的,这种隔离性使得你可以自由的操作数据,而不会受到限制

RefCell

特性:

  1. 内部可变性:允许在不使用可变引用的情况下修改数据
  2. 运行时检查: 在运行时检查借用规则的正确性,而不是编译时,违反借用规则仍然会导致panic
  3. 运行时开销:运行时的借用检查和 panic捕获会导致额外的开销,因此尽量在编译时确定借用规则
  4. 多线程不安全:非线程安全,不能在多线程中使用

应用场景:

  1. 避免借用规则限制:RefCell 允许在特定情况下绕过 Rust 的借用规则,使得在不可变引用的同时修改数据成为可能。这在某些场景下非常有用,特别是当你需要在不可变引用的上下文中修改数据时。
  2. 运行时检查错误:RefCell 在运行时进行借用检查,可以捕获到违反借用规则的错误,并在发生错误时引发 panic。这有助于在开发过程中及时发现和修复问题。
  3. 灵活性:相比于使用可变引用,使用 RefCell 可以更灵活地管理数据的可变性。RefCell 允许在需要时暂时获取可变引用,而不需要在整个作用域中保持可变引用的所有权。

为什么使用

  1. 提供借用灵活性,运行时检查而不是编译时
  2. 数据是不可变的,但是允许内部可变性

演示代码

fn main() {
    println!("Hello, 本案例演示了 RefCell<T> 和内部可变性模式!");
    let value = RefCell::new(String::from("Hello world"));
    // 通过调用borrow_mut()方法获取可变借用,去改变String的值
    value.borrow_mut().push('!');
    println!("{}", value.into_inner())
}

Cell 和 RefCell异同点

  1. 工作方式

    • Cell<T> 提供了 setget 方法来修改和获取内部的值。它通过复制和移动值来实现这一点,因此它只能用于 Copy trait 的类型。
    • RefCell<T> 则提供了 borrowborrow_mut 方法来获取内部值的不可变引用和可变引用。它使用运行时借用检查来保证借用规则的遵守,如果违反了借用规则(例如,同时存在多个可变引用或者同时存在可变引用和不可变引用),程序会在运行时崩溃。
  2. 使用场景

    • Cell<T> 适用于需要在不可变引用的上下文中修改值,但该值是 Copy trait 类型的情况。
    • RefCell<T> 适用于需要在运行时改变某个值,但不能或不想改变该值的所有权或可变性的场景,例如,需要在运行时根据条件修改值,或者需要在多个地方共享并修改同一个值。
  3. 性能

    • Cell<T> 的所有操作都是在编译时完成的,没有运行时开销。
    • RefCell<T> 则有一些运行时开销,因为它需要在运行时检查借用规则。

总的来说,Cell<T>RefCell<T> 都可以在保持 Rust 的所有权和借用规则的同时,提供一种在不可变引用的上下文中修改值的方式。Cell<T> 更简单,性能更好,但只能用于 Copy trait 的类型。而 RefCell<T> 则更灵活,可以用于任何类型,但有一些运行时开销,并且如果使用不当,可能会导致运行时错误。

Rc

Rc 是一种用于共享不可变数据的引用计数指针。它允许多个指针共享同一个数据,并且在没有任何所有者时自动释放数据。使用 Rc 可以简化对共享数据的所有权管理,并提供一种有效的方式来处理循环引用和共享不可变数据的情况。

特性:

  1. 引用计数自动管理,支持对不可变引用进行自动的增加(clone方法)或者减少(引用在作用域结束的时候)引用计数次数
  2. 共享所有权,Rc 允许多个 Rc 指针共享同一个数据,通过Rc::Clone方法使用

使用场景方面:

  1. 共享不可变数据:当需要在多个地方共享不可变数据时(实际就是读取数据的时候),可以使用 Rc。Rc 允许多个 Rc 指针同时拥有对数据的不可变引用,而不需要使用可变引用。
  2. 循环引用:当存在循环引用的情况,即 A 指向 B,B 又指向 A,而且它们之间都使用了 Rc 指针时,Rc 可以用于解决内存泄漏的问题。通过使用 Rc,可以在不需要数据时适时释放内存。

为什么使用 Rc:

  1. 共享不可变数据:Rc 允许多个指针共享不可变数据,这在某些场景下非常有用。例如,当多个部分需要访问同一个只读数据时,使用 Rc 可以避免数据的多次拷贝和所有权转移。
  2. 简化所有权管理:Rc 可以简化对共享数据的所有权管理。它允许多个所有者同时拥有对数据的引用,而不需要在代码中显式传递所有权或使用可变引用。

原始指针

原始指针(Raw Pointer)是指直接操作内存地址的指针,它们通常不受语言的类型系统和安全检查的限制。在 Rust 中,原始指针主要有两种类型:*const T*mut T,分别表示不可变和可变的原始指针。

Rust 的引用与原始指针有一些相似之处,它们都是用于访问数据的指针。但是,引用在编译时进行了借用检查,确保了内存安全和数据访问的正确性。引用必须遵循借用规则,包括不允许空指针、不允许悬垂指针和不允许多重引用等。

相比之下,原始指针不受这些限制,它们可以指向任意内存地址,可以进行指针运算,可以忽略借用规则,这就增加了潜在的安全风险。原始指针的不安全性主要来自以下几个方面:

  1. 空指针:原始指针可以为 null,即指向无效的内存地址。当对空指针进行解引用时,会导致程序崩溃或产生未定义行为。
  2. 悬垂指针:原始指针可以在其所指向的数据被释放后仍然存在。当悬垂指针被解引用时,会访问到无效的内存,导致未定义行为。
  3. 内存安全:原始指针没有借用规则的限制,可以同时拥有多个可变指针,可能导致数据竞争和未定义行为。

虽然原始指针存在安全风险,但它们在某些特定的场景下仍然有应用的价值。以下是一些原始指针的应用场合:

  1. 与外部代码交互:当需要与非 Rust 语言编写的库或代码进行交互时,可能需要使用原始指针来传递数据。
  2. 低级编程和底层操作:在进行低级编程或进行底层操作时,可能需要直接访问内存地址来实现特定的功能。
  3. FFI(Foreign Function Interface):在与 C 或其他语言进行 FFI 调用时,原始指针通常是传递数据的常用方式。

需要强调的是,使用原始指针需要非常小心,并且需要在必要时使用 unsafe 关键字来标记包含原始指针操作的代码块。这样可以将不安全的操作限制在有限的范围内,并确保在使用原始指针时仍然遵循 Rust 的安全规则。

总结起来,原始指针是直接操作内存地址的指针,与 Rust 的引用相比,原始指针不受类型系统和安全检查的限制。原始指针存在安全风险,包括空指针、悬垂指针和内存安全问题。然而,在与非 Rust 代码交互、低级编程和底层操作以及 FFI 调用等场景下,原始指针仍然有一定的应用价值,但需要小心使用,并使用 unsafe 关键字标记相关的不安全操作。

// ... 更多功能等待实现 ...

rust智能指针使用总结

Box

特性:

  1. 在堆上分配内存去存储T,并返回一个指向该内存的指针
  2. 单一所有权(只能有一个 Box 指向堆上的数据,拥有T的所有权,T不再拥有所有权),只有这个 Box 对象才能访问和修改存储在堆上的数据,避免了数据竞争和并发问题
  3. 离开作用域之后自动释放T内存,或者手动调用drop()方法释放
  4. 在编译时已知大小(它是一个指向堆上分配的值的指针),但可以存储未知大小的数据(存储的是指向堆上数据的指针,而不是数据本身)

应用场景:

  1. 递归数据结构,Box是一个非常好的选择。例如,你可以使用Box来创建一个链表或者树形结构。
  2. 动态大小类型 当你需要在堆上分配一些动态大小的类型(比如trait对象)时,Box很有用
  3. 处理大型数据 ,使用Box<T>可以提供一些额外的性能优势

为什么使用:

  1. 内存管理:Rust的所有权模型要求在编译时知道所有变量的大小,但有时候我们可能需要在运行时动态地分配内存。Box提供了一种在堆上分配内存的简单方式,并且当Box离开作用域时,它会自动清理其占用的内存。
  2. 抽象:Box可以用来创建trait对象,这是一种在运行时进行多态的方式。这允许我们编写能够处理不同类型的代码,而这些类型都实现了一个共同的trait。

Cell

Cell<T> 只能用于 Copy 类型,基本的数字类型、布尔类型、字符类型等都实现了 Copy trait

原理:

get操作的时候:获取 Cell 内部的 T 类型的值的位复制,返回这个复制的值

set操作的时候:接受一个 T 类型的值作为参数,使用这个值替换 Cell 内部的当前值

为什么要求一定实现 Copy trait?

如果不这样约定,

  1. Cell的get方法就会返回T的引用,就不能随便对其进行修改操作了,因为rust对引用有要求只能存在一个可变引用和多个不可变引用,我们多次调用set或者get的时候实际上获取了多个可变引用或者多个不可变引用,违背了rust的借用规则
  2. 在set的时候,会对T值进行修改,会导致之前get的值发生了改变,因为是同一个引用

如果使用Copy trait,上面的问题都不会出现

  1. get方法返回的是T数据的副本,并不会影响原来值的所有权
  2. set的时候是对原来的值进行替换修改,也不会影响原来值的所有权

get 和set操作实际都不是真正对原始值进行操作的,这种隔离性使得你可以自由的操作数据,而不会受到限制

RefCell

特性:

  1. 内部可变性:允许在不使用可变引用的情况下修改数据
  2. 运行时检查: 在运行时检查借用规则的正确性,而不是编译时,违反借用规则仍然会导致panic
  3. 运行时开销:运行时的借用检查和 panic捕获会导致额外的开销,因此尽量在编译时确定借用规则
  4. 多线程不安全:非线程安全,不能在多线程中使用

应用场景:

  1. 避免借用规则限制:RefCell 允许在特定情况下绕过 Rust 的借用规则,使得在不可变引用的同时修改数据成为可能。这在某些场景下非常有用,特别是当你需要在不可变引用的上下文中修改数据时。
  2. 运行时检查错误:RefCell 在运行时进行借用检查,可以捕获到违反借用规则的错误,并在发生错误时引发 panic。这有助于在开发过程中及时发现和修复问题。
  3. 灵活性:相比于使用可变引用,使用 RefCell 可以更灵活地管理数据的可变性。RefCell 允许在需要时暂时获取可变引用,而不需要在整个作用域中保持可变引用的所有权。

为什么使用

  1. 提供借用灵活性,运行时检查而不是编译时
  2. 数据是不可变的,但是允许内部可变性

演示代码

fn main() {
    println!("Hello, 本案例演示了 RefCell<T> 和内部可变性模式!");
    let value = RefCell::new(String::from("Hello world"));
    // 通过调用borrow_mut()方法获取可变借用,去改变String的值
    value.borrow_mut().push('!');
    println!("{}", value.into_inner())
}

Cell 和 RefCell异同点

  1. 工作方式

    • Cell<T> 提供了 setget 方法来修改和获取内部的值。它通过复制和移动值来实现这一点,因此它只能用于 Copy trait 的类型。
    • RefCell<T> 则提供了 borrowborrow_mut 方法来获取内部值的不可变引用和可变引用。它使用运行时借用检查来保证借用规则的遵守,如果违反了借用规则(例如,同时存在多个可变引用或者同时存在可变引用和不可变引用),程序会在运行时崩溃。
  2. 使用场景

    • Cell<T> 适用于需要在不可变引用的上下文中修改值,但该值是 Copy trait 类型的情况。
    • RefCell<T> 适用于需要在运行时改变某个值,但不能或不想改变该值的所有权或可变性的场景,例如,需要在运行时根据条件修改值,或者需要在多个地方共享并修改同一个值。
  3. 性能

    • Cell<T> 的所有操作都是在编译时完成的,没有运行时开销。
    • RefCell<T> 则有一些运行时开销,因为它需要在运行时检查借用规则。

总的来说,Cell<T>RefCell<T> 都可以在保持 Rust 的所有权和借用规则的同时,提供一种在不可变引用的上下文中修改值的方式。Cell<T> 更简单,性能更好,但只能用于 Copy trait 的类型。而 RefCell<T> 则更灵活,可以用于任何类型,但有一些运行时开销,并且如果使用不当,可能会导致运行时错误。

Rc

Rc 是一种用于共享不可变数据的引用计数指针。它允许多个指针共享同一个数据,并且在没有任何所有者时自动释放数据。使用 Rc 可以简化对共享数据的所有权管理,并提供一种有效的方式来处理循环引用和共享不可变数据的情况。

特性:

  1. 引用计数自动管理,支持对不可变引用进行自动的增加(clone方法)或者减少(引用在作用域结束的时候)引用计数次数
  2. 共享所有权,Rc 允许多个 Rc 指针共享同一个数据,通过Rc::Clone方法使用

使用场景方面:

  1. 共享不可变数据:当需要在多个地方共享不可变数据时(实际就是读取数据的时候),可以使用 Rc。Rc 允许多个 Rc 指针同时拥有对数据的不可变引用,而不需要使用可变引用。
  2. 循环引用:当存在循环引用的情况,即 A 指向 B,B 又指向 A,而且它们之间都使用了 Rc 指针时,Rc 可以用于解决内存泄漏的问题。通过使用 Rc,可以在不需要数据时适时释放内存。

为什么使用 Rc:

  1. 共享不可变数据:Rc 允许多个指针共享不可变数据,这在某些场景下非常有用。例如,当多个部分需要访问同一个只读数据时,使用 Rc 可以避免数据的多次拷贝和所有权转移。
  2. 简化所有权管理:Rc 可以简化对共享数据的所有权管理。它允许多个所有者同时拥有对数据的引用,而不需要在代码中显式传递所有权或使用可变引用。

原始指针

原始指针(Raw Pointer)是指直接操作内存地址的指针,它们通常不受语言的类型系统和安全检查的限制。在 Rust 中,原始指针主要有两种类型:*const T*mut T,分别表示不可变和可变的原始指针。

Rust 的引用与原始指针有一些相似之处,它们都是用于访问数据的指针。但是,引用在编译时进行了借用检查,确保了内存安全和数据访问的正确性。引用必须遵循借用规则,包括不允许空指针、不允许悬垂指针和不允许多重引用等。

相比之下,原始指针不受这些限制,它们可以指向任意内存地址,可以进行指针运算,可以忽略借用规则,这就增加了潜在的安全风险。原始指针的不安全性主要来自以下几个方面:

  1. 空指针:原始指针可以为 null,即指向无效的内存地址。当对空指针进行解引用时,会导致程序崩溃或产生未定义行为。
  2. 悬垂指针:原始指针可以在其所指向的数据被释放后仍然存在。当悬垂指针被解引用时,会访问到无效的内存,导致未定义行为。
  3. 内存安全:原始指针没有借用规则的限制,可以同时拥有多个可变指针,可能导致数据竞争和未定义行为。

虽然原始指针存在安全风险,但它们在某些特定的场景下仍然有应用的价值。以下是一些原始指针的应用场合:

  1. 与外部代码交互:当需要与非 Rust 语言编写的库或代码进行交互时,可能需要使用原始指针来传递数据。
  2. 低级编程和底层操作:在进行低级编程或进行底层操作时,可能需要直接访问内存地址来实现特定的功能。
  3. FFI(Foreign Function Interface):在与 C 或其他语言进行 FFI 调用时,原始指针通常是传递数据的常用方式。

需要强调的是,使用原始指针需要非常小心,并且需要在必要时使用 unsafe 关键字来标记包含原始指针操作的代码块。这样可以将不安全的操作限制在有限的范围内,并确保在使用原始指针时仍然遵循 Rust 的安全规则。

总结起来,原始指针是直接操作内存地址的指针,与 Rust 的引用相比,原始指针不受类型系统和安全检查的限制。原始指针存在安全风险,包括空指针、悬垂指针和内存安全问题。然而,在与非 Rust 代码交互、低级编程和底层操作以及 FFI 调用等场景下,原始指针仍然有一定的应用价值,但需要小心使用,并使用 unsafe 关键字标记相关的不安全操作。

// ... 更多功能等待实现 ...

版权属于:admin 所有,采用《知识共享署名许可协议》进行许可,转载请注明文章来源。

本文链接: http://520code.net/index.php/archives/5/

赞 (2)

评论区(暂无评论)

这里空空如也,快来评论吧~

我要评论