Rust的问题还是很多,主要是一些关键的使用方法太多了,需要知道什么时候用,为什么用是非常关键的,下面罗列的是基本上贯穿《Rust程序设计 - 第二版》的所有难点理解内容。为了将这些内容融会贯通,需要非常多的例子总结才能理解。下面内容就开始对其中的内容一个一个来梳理。
Box、AsRef(as_ref)、AsMut(as_mut)、borrow、borrow_mut、from、into、try_from、try_into、toOwned
动态大小类型(DST,Dynamically Sized Types)不能直接作为函数参数,也不能作为函数的返回值。所以需要有手段来解决这个问题
当一个Vec或者某个对象需要在栈中分配,但无法知道其大小,编译器其实很难搞,所以需要用Box将对象分配到堆上面。几种场景,第一个是作为结构体的成员,第二个是作为入参数,第三个是作为返回值。
它们分别用于将一个类型转换为引用和可变引用。
AsRef 特性允许将一个类型转换为另一个类型的引用。具体来说,AsRef<Path> 表示可以将一个类型转换为 Path 的引用。标准库中已经为许多常见类型实现了这些特性,其中包括 &str 转换为 &Path,是因为&str 实现了 AsRef<Path>。AsRef 特性允许你将一个类型转换为另一个类型的引用的特点,这在编写泛型代码时特别有用,因为它使得你的代码能够接受更广泛的输入类型。
let path = "example.txt";
let content = as_ref_test(path);
print!("{}", content.unwrap());
pub fn as_ref_test<P:AsRef<Path>>(s:P) -> Result<String>{
Ok("good".to_string())
}
以及其他例子:
fn takes_asref_u8_slice<S: AsRef<[u8]>>(s: S) {
let slice: &[u8] = s.as_ref();
println!("Slice: {:?}", slice);
}
fn main() {
let vec = vec![1, 2, 3];
let array = [1, 2, 3];
let string = String::from("hello");
let str_slice = "hello";
takes_asref_u8_slice(vec);
takes_asref_u8_slice(&array);
takes_asref_u8_slice(string);
takes_asref_u8_slice(str_slice);
}
只要一个类型实现了 AsRef 特性,你就可以直接调用 as_ref 方法:
use std::path::Path;
fn main() {
let s = "example.txt";
let path: &Path = s.as_ref(); // 这里进行隐式转换
println!("Path: {:?}", path);
}
以及其他类型:
fn main() {
let vec = vec![1, 2, 3];
let slice: &[u8] = vec.as_ref(); // 这里进行隐式转换
println!("Slice: {:?}", slice);
}
可以将 as_ref 看作是一种简化类型转换的工具,使代码更加灵活和通用。 也可以自己实现as_ref
struct MyStructS {
value: i32,
}
impl AsRef<i32> for MyStructS {
fn as_ref(&self) -> &i32 {
&self.value
}
}
let ms = MyStructS{value:32};
let val_ms = ms.as_ref();
println!("{}", val_ms)
AsMut 特性与 AsRef 类似,但它允许将一个类型转换为其可变引用类型。AsMut 特性提供了 as_mut 方法,用于获取可变引用。
fn main() {
let mut vec = vec![1, 2, 3];
let slice: &mut [u8] = vec.as_mut(); // 这里进行隐式转换
slice[0] = 10;
println!("Modified vec: {:?}", vec);
}
以及
fn main() {
let mut boxed = Box::new(42);
let reference: &mut i32 = boxed.as_mut(); // 这里进行隐式转换
*reference += 1;
println!("Modified value: {}", boxed);
}
我们可以直接调用 boxed.as_mut() 将其转换为 &mut i32,然后通过这个可变引用修改原始值。
也可以自己实现:
struct MyStruct {
value: i32,
}
impl AsMut<i32> for MyStruct {
fn as_mut(&mut self) -> &mut i32 {
&mut self.value
}
}
fn main() {
let mut my_struct = MyStruct { value: 42 };
let value_ref: &mut i32 = my_struct.as_mut(); // 这里进行隐式转换
*value_ref += 1;
println!("Modified value: {}", my_struct.value);
}
因为 s 本身就是一个 &str 类型,所以 s.as_ref() 返回的也是一个 &str 类型的引用,并不是引用的引用。
fn main() {
let s: &str = "hello";
let s_ref: &str = s.as_ref(); // 这里 s_ref 也是 &str
println!("s_ref: {}", s_ref);
}
AsRef 是一个特性(trait),它允许你将一个类型转换为某个引用类型。使用 AsRef 可以使函数接受多种输入类型,只要这些类型实现了 AsRef 特性。这种方式通常用于泛型编程,提供了编译时的多态性。
use std::convert::AsRef;
fn print_as_str<T: AsRef<str>>(input: T) {
let s: &str = input.as_ref();
println!("{}", s);
}
fn main() {
let s = String::from("Hello, world!");
let s_ref: &str = "Hello, world!";
print_as_str(s); // String 实现了 AsRef<str>
print_as_str(s_ref); // &str 实现了 AsRef<str>
}
dyn 用于定义动态分发的特性对象。它允许你在运行时决定调用哪个实现,而不是在编译时。这种方式提供了运行时的多态性,但通常会带来一些性能开销,因为它需要通过虚表(vtable)进行动态分发。
trait Printable {
fn print(&self);
}
impl Printable for String {
fn print(&self) {
println!("{}", self);
}
}
impl Printable for &str {
fn print(&self) {
println!("{}", self);
}
}
fn print_dyn(input: &dyn Printable) {
input.print();
}
fn main() {
let s = String::from("Hello, world!");
let s_ref: &str = "Hello, world!";
print_dyn(&s); // String 实现了 Printable
print_dyn(&s_ref); // &str 实现了 Printable
}
在这个示例中,print_dyn 函数接受一个特性对象 &dyn Printable,它可以在运行时调用适当的 print 方法。String 和 &str 都实现了 Printable 特性,因此可以传递给这个函数。
选择使用 AsRef 还是 dyn 取决于你的具体需求和场景。如果你需要编译时的多态性和更高的性能,可以考虑使用 AsRef。如果你需要运行时的灵活性,可以考虑使用 dyn。
std::borrow::Borrow 和 std::borrow::BorrowMut 是两个不同的特性(traits),它们分别用于不可变借用和可变借用。
如果一个类型实现了 Borrow<T>,那么它的 borrow 方法就能高效地从自身借入一个 &T。但是 Borrow 施加了更多限制:只有当 &T 能通过与它借来的 值相同的方式进行哈希和比较时,此类型才应实现 Borrow<T>。 (Rust 并不强制执行此限制,它只是记述了此特型的意图。)这使得 Borrow 在处理哈希表和树中的键或者处理因为某些原因要进行哈希 或比较的值时非常有用。
Borrow提供了一种将类型转换为某个引用类型的机制。这个特性主要用于泛型编程,允许你编写能够接受多种类型的代码,只要这些类型实现了 Borrow 特性。
use std::collections::HashMap;
use std::borrow::Borrow;
fn main() {
let mut map: HashMap<String, i32> = HashMap::new();
map.insert("one".to_string(), 1);
map.insert("two".to_string(), 2);
// 使用 &str 查找 HashMap<String, i32>
let key: &str = "one";
if let Some(value) = map.get(key.borrow()) {
println!("The value for '{}' is {}", key, value);
} else {
println!("Key '{}' not found", key);
}
}
例如:这里的代码中:y和z有什么区别?
let key: &str = "one";
let mut y = &key;
let z = key.borrow();
y 是一个对 key 的引用的引用,因此可以重新绑定以指向不同的字符串切片引用。而 z 是一个直接的字符串切片引用,因此它与 key 本身相同。
use std::borrow::Borrow;
fn main() {
let key: &str = "one";
let mut y = &key;
let z = key.borrow();
println!("key: {}", key); // 输出: key: one
println!("y: {}", y); // 输出: y: one
println!("z: {}", z); // 输出: z: one
// 修改 y 以指向一个新的 &str 引用
let new_key: &str = "two";
y = &new_key;
println!("y after modification: {}", y); // 输出: y after modification: two
}
你会发现下面的代码报错:
let key: &str = "one";
let z = key.borrow();
*z = "sdf".to_string();
为了解决上面的报错,接下来介绍下Borrow_mut。
实现了 BorrowMut 特性的类型可以通过 borrow_mut 方法获取一个对某个类型的可变引用。这个特性允许你在需要修改数据时获取可变引用。
let mut key: String = "one".to_string();
let z: &mut String = key.borrow_mut();
*z = "sdf".to_string();
使用 String 类型而不是 &str,因为 String 是可变的。使用 borrow_mut 方法获取可变引用,然后通过该引用修改数据。
在 Rust 中,&str 是一个不可变的引用,表示对字符串切片的不可变借用。因此,不能对 &str 进行可变借用(即不能调用 borrow_mut 方法)。
表示 类型转换,这种转换会接受一种类型的值并返回另一种类型的值。AsRef 特型和 AsMut 特型用于从一种类型借入另一种类型的引用, 而 From 和 Into 会获取其参数的所有权,对其进行转换,然后将 转换结果的所有权返回给调用者。
From 特性用于定义一种类型如何从另一种类型转换过来。通常,你会为目标类型实现 From 特性。
use std::convert::From;
struct MyStruct {
value: i32,
}
impl From<i32> for MyStruct {
fn from(item: i32) -> Self {
MyStruct { value: item }
}
}
fn main() {
let num = 42;
let my_struct = MyStruct::from(num);
println!("MyStruct value: {}", my_struct.value);
}
在这个示例中,我们没有显式地为 MyStruct 实现 Into<i32>,但由于我们已经实现了 From<i32>,所以我们可以使用 num.into() 将 i32 类型的值转换为 MyStruct。
Into 特性是 From 特性的对偶。如果你为一个类型实现了 From 特性,那么 Into 特性会自动为你实现。
use std::convert::Into;
struct MyStruct {
value: i32,
}
impl From<i32> for MyStruct {
fn from(item: i32) -> Self {
MyStruct { value: item }
}
}
fn main() {
let num = 42;
let my_struct: MyStruct = num.into();
println!("MyStruct value: {}", my_struct.value);
}
如果你为类型 T 实现了 From<U>,那么你可以自动将 T 转换为 U,因为 Into<U> 会自动为 T 实现。
由于转换的行为方式不够清晰,因此 Rust 没有为 i32 实现 From<i64>,也没有实现任何其他可能丢失信息的数值类型之间的转 换,而是为 i32 实现了 TryFrom<i64>。TryFrom 和 TryInto 是 From 和 Into 的容错版“表亲”,这种转换同样是双向的,实 现了 TryFrom 也就意味着实现了 TryInto。
TryFrom 特性用于定义一种类型如何尝试从另一种类型转换过来。与 From 不同,TryFrom 的转换可能会失败,因此返回一个 Result 类型。
use std::convert::TryFrom;
struct EvenNumber(i32);
impl TryFrom<i32> for EvenNumber {
type Error = &'static str;
fn try_from(value: i32) -> Result<Self, Self::Error> {
if value % 2 == 0 {
Ok(EvenNumber(value))
} else {
Err("Value is not an even number")
}
}
}
fn main() {
let even = EvenNumber::try_from(8);
let odd = EvenNumber::try_from(3);
match even {
Ok(num) => println!("Even number: {}", num.0),
Err(e) => println!("Error: {}", e),
}
match odd {
Ok(num) => println!("Even number: {}", num.0),
Err(e) => println!("Error: {}", e),
}
}
TryInto 特性是 TryFrom 特性的对偶。如果你为一个类型实现了 TryFrom 特性,那么 TryInto 特性会自动为你实现。
// 溢出时饱和,而非回绕
let smaller: i32 = huge.try_into().unwrap_or(i32::MAX);
try_into() 方法给了我们一个 Result,因此我们可以选择在异 常情况下该怎么做。
use std::convert::TryInto;
struct EvenNumber(i32);
impl TryFrom<i32> for EvenNumber {
type Error = &'static str;
fn try_from(value: i32) -> Result<Self, Self::Error> {
if value % 2 == 0 {
Ok(EvenNumber(value))
} else {
Err("Value is not an even number")
}
}
}
fn main() {
let even: Result<EvenNumber, _> = 8i32.try_into();
let odd: Result<EvenNumber, _> = 3i32.try_into();
match even {
Ok(num) => println!("Even number: {}", num.0),
Err(e) => println!("Error: {}", e),
}
match odd {
Ok(num) => println!("Even number: {}", num.0),
Err(e) => println!("Error: {}", e),
}
}
在 Rust 中,函数不能直接返回 str 和 [u] 这样的类型,因为它们是动态大小类型(DSTs)。动态大小类型在编译时无法确定其大小,因此无法直接在栈上分配空间。为了更好地理解这个问题,我们需要深入了解 Rust 的类型系统和内存管理模型。
ToOwned 特性用于将借用类型转换为拥有类型。它通常与 Borrow 特性一起使用,以便在需要时从借用转换为拥有。
use std::borrow::ToOwned;
impl ToOwned for str {
type Owned = String;
fn to_owned(&self) -> String {
String::from(self)
}
}
impl<T: Clone> ToOwned for [T] {
type Owned = Vec<T>;
fn to_owned(&self) -> Vec<T> {
self.to_vec()
}
}
在这个示例中,str 实现了 ToOwned,其拥有类型是 String。这意味着你可以将一个 &str 转换为一个 String。同样,[T] 实现了 ToOwned,其拥有类型是 Vec<T>,前提是 T 实现了 Clone。
use std::path::{Path, PathBuf};
fn main() {
// 创建一个 PathBuf
let path_buf = PathBuf::from("/some/path");
// 从 PathBuf 借用一个 Path
let path: &Path = path_buf.borrow();
println!("Borrowed Path: {:?}", path);
// 将 Path 转换为 PathBuf
let owned_path_buf: PathBuf = path.to_owned();
println!("Owned PathBuf: {:?}", owned_path_buf);
}
Vec<T>与[T]、str与String、Path与PathBuf。
实现关系:拥有类型必须实现 Borrow<Self>,这样可以从拥有类型借用到自身的借用类型。同时,借用类型可以实现 ToOwned,以便在需要时转换为拥有类型。
要想用好 Rust,就必然涉及对所有权问题的透彻思考,比如函数应该 通过引用还是值接受参数。通常你可以任选一种方式,让参数的类型 反映你的决定。但在某些情况下,在程序开始运行之前你无法决定是 该借用还是该拥有,std::borrow::Cow 类型(用于“写入时克 隆”,clone on write 的缩写)提供了一种兼顾两者的方式。
enum Cow<'a, B: ?Sized>
where B: ToOwned
{
Borrowed(&'a B),
Owned(<B as ToOwned>::Owned),
}
怎么理解:
泛型参数和生命周期:
枚举变体:
use std::borrow::Cow;
fn main() {
let borrowed: Cow<str> = Cow::Borrowed("Hello, world!");
let owned: Cow<str> = Cow::Owned(String::from("Hello, world!"));
match borrowed {
Cow::Borrowed(s) => println!("Borrowed: {}", s),
Cow::Owned(s) => println!("Owned: {}", s),
}
match owned {
Cow::Borrowed(s) => println!("Borrowed: {}", s),
Cow::Owned(s) => println!("Owned: {}", s),
}
}
怎么使用?
struct Config<'a> {
data: Cow<'a, str>,
}
impl<'a> Config<'a> {
fn new(data: Cow<'a, str>) -> Self {
Config { data }
}
fn process(&self) {
// 处理配置数据
println!("Processing config: {}", self.data);
}
}
接下来,展示如何使用 Config 结构体处理借用的和拥有的配置数据:
fn main() {
// 借用字符串切片作为配置数据
let config_data1 = "max_connections=100";
let config1 = Config::new(Cow::Borrowed(config_data1));
config1.process();
// 拥有字符串数据作为配置数据
let config_data2 = String::from("max_connections=200");
let config2 = Config::new(Cow::Owned(config_data2));
config2.process();
}