您好,登录后才能下订单哦!
本篇内容主要讲解“Rust常用的标准库工具有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Rust常用的标准库工具有哪些”吧!
我们一般使用use
语句把其他模块的内容引入当前模块中,但对于标准库中一些非常常用的工具,每次都写use
语句就过于冗余,因此标准库提供了一个std::prelude
模块,在这个模块中导出一些最常见的工具,然后编译器为用户编写的每一个crate都自动插入一句话:
use std::prelude::*;
如此,一些最常见的工具就可以直接使用,而无需use
了。对这个std::prelude
模块所包含内容的权衡是需要细致考量的,因为如果包含的内容过多则会引入很多不被使用的工具,这反倒不美了。当下的std::prelude
模块中包含的内容在std::prelude::v1
中。
除了as
关键字可以用来进行基本类型之间的转换,Rust还提供了很多trait来实现自定义类型之间的转换。
AsRef
的含义是该类型可以通过调用as_ref
方法得到另外一个类型的共享引用,同理,AsMut
得到的是另外一个类型的可读写引用,它们的定义如下:
pub trait AsRef<T: ?Sized> { fn as_ref(&self) -> &T; } pub trait AsMut<T: ?Sized> { fn as_mut(&mut self) -> &mut T; }
AsRef
很适合用于泛型代码中,例如,下面的泛型函数接受各种类型,只要可以被转换为&[u8]
即可:
fn iter_bytes<T: AsRef<[u8]>>(arg: &T) { for i in arg.as_ref() { println!("{}", i); } } fn main() { let s = String::from("this is a string"); let v = vec![1, 2, 3]; let c = "hello"; iter_bytes(&s); iter_bytes(&v); iter_bytes(&c); }
Borrow
、BorrowMut
这两个trait的设计和AsRef
、AsMut
很类似:
pub trait Borrow<Borrowed: ?Sized> { fn borrow(&self) -> &Borrowed; } pub trait BorrowMut<Borrowed: ?Sized>: Borrow<Borrowed> { fn borrow_mut(&mut self) -> &mut Borrowed; }
但区别在于两点:
标准库为所有的T
、&T
和&mut T
默认实现了Borrow
、BorrowMut
,以Borrow
为例:
impl<T: ?Sized> Borrow<T> for T { fn borrow(&self) -> &T { self } } impl<T: ?Sized> Borrow<T> for &T { fn borrow(&self) -> &T { &**self } } impl<T: ?Sized> Borrow<T> for &mut T { fn borrow(&self) -> &T { &**self } }
Borrow
要求返回的类型,必须和原来的类型具备同样的hash值。这是一个约定,如果违反了这个约定,那么把这个类型放到HashMap
里时可能会出现问题。
AsRef
和Borrow
都是从&T
到&U
的转换,而From/Into
是从T
到U
的转换:
pub trait From<T>: Sized { fn from(_: T) -> Self; } pub trait Into<T>: Sized { fn into(self) -> T; }
由于From
和Into
是互逆的一组转换,因此标准库提供了这样一个实现:
impl<T, U> Into<U> for T where U: From<T>, { fn into(self) -> U { U::from(self) } }
这段代码的含义是:如果存在U: From<T>
,则为类型T
实现Into<U>
。也就是说,我们只需为类型实现From
即可,Into
会自动实现。标准库中还有一组对应的TryFrom
和TryInto
,他们是为了处理类型转换过程中可能发生转换错误的情况,因此返回值是Result
类型。
ToOwned
提供一种更泛化的Clone
的功能,Clone
是从&T
类型变量创造一个新的T
类型变量,而ToOwned
是从一个&T
类型变量创造一个新的U
类型变量,标准库中也提供了ToOwned
调用clone
方法的默认实现:
pub trait ToOwned { type Owned: Borrow<Self>; fn to_owned(&self) -> Self::Owned; fn clone_into(&self, target: &mut Self::Owned) { *target = self.to_owned(); } } impl<T> ToOwned for T where T: Clone, { type Owned = T; fn to_owned(&self) -> T { self.clone() } fn clone_into(&self, target: &mut T) { target.clone_from(self); } }
ToString
提供了其他类型转换为String
类型的能力:
pub trait ToString { fn to_string(&self) -> String; }
标准库中为所有实现了Display
trait的类型默认实现了ToString
,而Display
可以通过derive实现:
impl<T: fmt::Display + ?Sized> ToString for T { default fn to_string(&self) -> String { use fmt::Write; let mut buf = String::new(); buf.write_fmt(format_args!("{}", self)) .expect("a Display implementation returned an error unexpectedly"); buf } }
FromStr
提供了从字符串切片向其他类型转换的能力:
pub trait FromStr: Sized { type Err; fn from_str(s: &str) -> Result<Self, Self::Err>; }
这里的IO指的是标准输入输出和文件输入输出。
我们之前介绍过的println!
宏可以方便地随手输出一些信息,但如果要对标准输入输出作更精细的控制,则需要调用std::io::stdin()
函数和std::io::stdout()
函数来获取Stdin
和Stdout
结构体的实例。这两个实例的简单用法如下:
use std::io::{self, Read}; fn main() -> io::Result<()> { let mut buffer = String::new(); let mut stdin = io::stdin(); stdin.read_to_string(&mut buffer)?; Ok(()) }
为了线程安全考虑,每次读取操作都需要上锁,这降低了效率。解决的方法是手动调用lock()
方法,但这又增添了使用标准输入输出的复杂度。
事实上,我们受学生时代做各种C语言大作业的影响,导致我们认为标准输入输出是非常重要的功能,可我们细想一下,正儿八经的命令行程序谁会用标准输入来和用户交互呢?一般都是通过两种方式,一则是调用程序的时候指定参数,另一则是通过文件读取用户配置。
文件输入输出首先要解决路径问题。Rust中的字符串类型是String
和str
,它们都是用utf-8
进行编码的。但是,在具体的操作系统上并不是统一使用utf-8
编码,为了应付这种情况,Rust中设计了OsString
和OsStr
,这两种类型使用方法和String
和str
类似,并且它们之间也可以互相转换。
Rust标准库提供了std::path::PathBuf
和std::path::Path
来处理路径,PathBuf
对内部数据拥有所有权,而Path
只是借用,事实上,PathBuf
内部存储了一个OsString
,而Path
则是存储了Path
。
Rust的文件操作主要通过std::fs::File
来完成,可以实现打开、创建、复制等文件操作。对于文件的读写,就要用到std::io
模块中的一些trait了,例如Read
和Write
。File
实现了这 两个trait,因此拥有read
等读取文件的方法。
下面看一个例子来演示说明文件输入输出的方法:
use std::fs::File; use std::io::{BufRead, BufReader, Read}; fn test_read_file() -> Result<(), std::io::Error> { let mut path = std::env::current_dir().unwrap(); path.push("Cargo.toml"); let mut file = File::open(&path)?; let mut buffer = String::new(); file.read_to_string(&mut buffer)?; println!("{}", buffer); Ok(()) } fn main() { match test_read_file() { Ok(_) => {} Err(e) => { println!("{}", e); } } }
和C++的STL类似,Rust的标准库也给我们提供了一些比较常用的容器以及相关的迭代器,目前实现了的容器有:
容器 | 描述 |
---|---|
Vec | 可变长数组,连续存储 |
VecDeque | 双向队列,适用于从头部和尾部插入删除数据 |
LinkedList | 双向链表,非连续存储 |
HashMap | 基于Hash算法存储一系列键值对 |
BTreeMap | 基于B树存储一系列键值对 |
HashSet | 相当于没有值的HashMap |
BTreeSet | 相当于没有值的BTreeMap |
BinaryHeap | 基于二叉堆实现的优先级队列 |
这里不详细展开讲,以后会对各个容器的用法和实现原理进行深入探究。
Rust中的迭代器是指实现了std::iter::Iterator
这个trait的类型,其定义如下:
pub trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>; ... }
它最主要的方法是next()
,返回一个Option<Item>
,迭代完成则返回None。实现了Iterator
的类型可直接用于for循环。
迭代器拥有一个很重要的特性,就是它是可组合的,这有点类似于Java中的流式编程。Iterator
中有很多方法,它们返回的类型也实现了Iterator
,这意味着,我们调用这些方法可以从一个迭代器创造出一个新的迭代器,例如:
fn main() { let v = vec![1, 2, 3, 4, 5, 6, 7, 8]; let mut iter = v .iter() .take(5) .filter(|&x| x % 2 == 0) .map(|&x| x * x) .enumerate(); while let Some((i, v)) = iter.next() { println!("{}: {}", i, v); } }
这段代码的含义是:从v
这个Vec
的前五个元素中筛选元素,要求它必须是2的倍数,并把该元素进行平方,因此,最终的输出结果是0: 4
和1: 16
。
Rust支持自定义类型重载部分运算符,只需该类型实现std::ops
模块下相应的trait即可。以Add
为例:
pub trait Add<Rhs = Self> { type Output; fn add(self, rhs: Rhs) -> Self::Output; }
它具备一个泛型参数RHS
和一个关联类型Output
。标准库中早已为基本的数字类型实现了这个trait:
macro_rules! add_impl { ($($t:ty)*) => ($( #[stable(feature = "rust1", since = "1.0.0")] impl Add for $t { type Output = $t; #[inline] #[rustc_inherit_overflow_checks] fn add(self, other: $t) -> $t { self + other } } forward_ref_binop! { impl Add, add for $t, $t } )*) } add_impl! { usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 f32 f64 }
到此,相信大家对“Rust常用的标准库工具有哪些”有了更深的了解,不妨来实际操作一番吧!这里是亿速云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。