1、使用newtype模式实现类型安全和抽象
newtype模式可以:
-用来静态的保证各种值之间不会混淆并表明值的单位
-为类型的某些细节提供抽象能力
-通过轻量级的封装来隐藏内部实现细节
使用类型别名创建类型同义词
Rust提供了类型别名的功能:
为现有类型生产另外的名称(同义词)
并不是一个独立的类型
-使用type关键字
主要用途:减少代码字符重复
type Kilometers = i32;
fn main() {
let x:i32 = 5;
let y:Kilometers = 5;
println!("x + y = {}",x + y);
}
我们来看另外一个例子:
type Thunk = Box<dyn Fn() + Send + 'static>;
fn takes_long_type(f:Thunk) {
// --snip
}
fn returns_long_type()->Thunk {
Box::new(||println!("hi"))
}
fn main() {
let f :Thunk = Box::new(||println!("Hi"));
}
把一个比较长的类型使用类型别名进行替换,这样就可以尽可能的避免出错。
// use std::io::Error;
use std::fmt;
// pub trait Write {
// fn write(&mut self,buf: &[u8])->Result<usize,Error>;
// fn flush(&mut self)->Result<(),Error>;
// fn write_all(&mut self,buf: &[u8])->Result<(),Error>;
// fn write_fmt(&mut self,fmt: fmt::Arguments)->Result<(),Error>;
// }
// type Result<T> = Result<T,std::io::Error>;
type Result<T> = std::io::Result<T>;
pub trait Write {
fn write(&mut self,buf: &[u8])->Result<usize>;
fn flush(&mut self)->Result<()>;
fn write_all(&mut self,buf: &[u8])->Result<()>;
fn write_fmt(&mut self,fmt: fmt::Arguments)->Result<()>;
}
fn main() {
}
Never类型
有一个名为!的特殊类型:
它没有任何值,行话称为空类型(empty type)
我们倾向于叫它never类型,因为它在不返回的函数中充当返回类型
不返回值的函数也被称作发散函数(divergingfunction)
fn bar()->! {
}
fn main() {
}
上面的代码中我们期待返回的是!但是在我们的函数中什么也没有返回,什么都没返回就表示返回()类型,所以上面的代码会报错,那么!类型有什么用呢?
// impl<T> Option<T> {
// pub fn unwrap(self) -> T {
// match self {
// Some(val) => val,
// None => panic!("called `Option::unwrap()` on a `None` value"),
// }
// }
// }
fn main() {
let guess = "";
loop {
let guess:u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
}
}
这里的continue表示返回的就是!(Nerver类型),整个表达式返回的就是u32的类型
上面注释的panic!返回的也是Nerver类型
fn main() {
print!("forever ");
loop {
print!("and ever")
}
}
上面代码中的loop它返回的也是一个Nerver类型,当然我们可以使用break进行跳出循环
动态大小和Sized Trait
Rust需要在编译时确定为--个特定类型的值分配多少空间。
动态大小的类型( Dynamically Sized Types,DST)的概念:
-编写代码时使用只有在运行时才能确定大小的值
str是动态大小的类型(注意不是&str) :只有运行时才能确定字符串的长度
-下列代码无法正常工作:
●lets1: str = "Hello there!";
●let s2: str = "How's it going?";
使用&str 来解决:
●str的地址
●str的长度
Rust使用动态大小类型的通用方式
附带一些额外的元数据来存储动态信息的大小.
-使用动态大小类型时总会把它的值放在某种指针后边
另外一种动态大小的类型: trait
每个trait都是--个动态大小的类型,可以通过名称对其进行引用
为了将trait用作trait对象,必须将它放置在某种指针之后
例如&dyn Trait或Box<dyn Trait> (Rc<dyn Trait>)之后
Sized trait
为了处理动态大小的类型,Rust 提供了-一个Sized trait来确定- - 个类型的大小在
编译时是否已知
编译时可计算出大小的类型会自动实现这--trait
Rust还会为每一个泛型函数隐式的添加Sized约束
// fn generic<T>(t:T) {
// }
// fn generic<T:Sized>(t:T) {
// }
fn main() {
}
上面注释的两行代码第一个函数会隐示的转换为第二个函数
默认情况下,泛型函数只能被用于编译时已经知道大小的类型,可以通过特殊语法
解除这- -限制
?Sized trait约束
// fn generic<T>(t:T) {
// }
// fn generic<T:Sized>(t:T) {
// }
// fn generic<T:?Sized>(t:&T) {
// }
fn main() {
}
fn add_one(x:i32) -> i32 {
x + 1
}
fn do_twice(f:fn(i32)->i32,arg:i32)->i32 {
f(arg) + f(arg)
}
fn main() {
let answer = do_twice(add_one, 5);
println!("The answer is:{}",answer);
}
函数指针与闭包的不同
fn是一个类型,不是一个trait
-可以直接指定fn为参数类型,不用声明一个以Fn trait 为约束的泛型参数
函数指针实现了全部3种闭包trait (Fn,FnMut,FnOnce) :
-总是可以把函数指针用作参数传递给--个接收闭包的函数
-所以,倾向于搭配闭包trait的泛型来编写函数:可以同时接收闭包和普通函数
-与外部不支持闭包的代码交互: C函数
fn main() {
let list_of_numbers = vec![1,2,3];
let list_of_strings:Vec<String> = list_of_numbers
.iter()
.map(|i|i.to_string())
.collect();
let list_of_numbers = vec![1,2,3];
let list_of_strings:Vec<String> = list_of_strings
.iter()
.map(ToString::to_string)
.collect();
}
上面的代码中我们能可以通过闭包来将i32类型的值转化为string也可以通过直接传递一个函数来进行转化
fn main() {
enum Status {
Value(u32),
Stop,
}
let v = Status::Value(3);
let list_of_statuses:Vec<Status> =
(0u32..20)
.map(Status::Value)
.collect();
}
返回闭包
闭包使用trait进行表达,无法在函数中直接返回一个闭包,可以将一一个实现了该
trait的具体类型作为返回值。
// fn returns_closure()->Fn(i32)->i32 {
// |x|x + 1
// }
fn returns_closure()->Box<dyn Fn(i32)->i32> {
Box::new(|x|x + 1)
}
fn main() {
}