Rust中我们要用’来表示一个char,如果用"的话你得到的实际上是一个&'static str。
Slice从直观上讲,是对一个Array的切片,通过Slice,你能获取到一个Array的部分或者全部的访问权限。和Array不同,Slice是可以动态的,但是呢,其范围是不能超过Array的大小,这点和Golang是不一样的。
let arr = [1, 2, 3, 4, 5, 6];
let slice_complete = &arr[..]; // 获取全部元素
let slice_middle = &arr[1..4]; // 获取中间元素,最后取得的Slice为 [2, 3, 4] 。切片遵循左闭右开原则。
let slice_right = &arr[1..]; // 最后获得的元素为[2, 3, 4, 5, 6],长度为5。
let slice_left = &arr[..3]; // 最后获得的元素为[1, 2, 3],长度为3。
let mut v1: Vec = vec![1, 2, 3]; // 通过vec!宏来声明let v2 = vec![0; 10]; // 声明一个初始长度为10的值全为0的动态数组
println!("{}", v1[0]); // 通过下标来访问数组元素for i in &v1 {
print!("{}", i); // &Vec 可以通过 Deref 转换成 &[i32]
}
println!("");for i in &mut v1 {
*i = *i+1;
print!("{}", i); // 可变访问
}
你可以用str来声明一个字符串,事实上,Rust中,所有用""包裹起来的都可以称为&str
let y = (2, "hello world");let x: (i32, &str) = (3, "world hello");
struct A {
attr1: i32,
atrr2: String,
}
struct B(i32, u16, bool);
struct D;
struct RefBoy<'a> {
loc: &'a i32,
}
这里解释一下这个符号<>,它表示的是一个属于的关系,无论其中描述的是生命周期还是泛型。即: RefBoy in 'a。最终我们可以得出个结论,RefBoy这个结构体,其生命周期一定不能比’a更长才行。
结构体里的引用字段必须要有显式的生命周期
一个被显式写出生命周期的结构体,其自身的生命周期一定小于等于其显式写出的任意一个生命周期
注:生命周期和泛型都写在<>里,先生命周期后泛型,用;分隔。
被move的self
struct A {
a: i32,
}
impl A {
pub fn show(self) {
println!("{}", self.a);
}
}
fn main() {
let ast = A{a: 12i32};
ast.show();
println!("{}", ast.a);
}
错误
13:25 error: use of moved value: `ast.a` [E0382]
:13 println!("{}", ast.a);
#[derive(Copy, Clone)]struct A {
a: i32,
}
这么写的话,会使编译通过。但是这么写实际上也是有其缺陷的。其缺陷就是:你不能在一个被copy的impl函数里改变它!事实上,被move的self其实是相对少用的一种情况,更多的时候,我们需要的是ref和ref mut。
需要注意的是,一旦你的结构体持有一个可变引用,你,只能在&mut self的实现里去改变他!
rust的枚举能做到的,比C语言的多很多。 比如,枚举里面居然能包含一些你需要的,特定的数据信息! 这是常规的枚举所无法做到的,更像枚举类,不是么?
enum SpecialPoint {
Point(i32, i32),Special(String),
}
enum SpecialPoint {
Point {
x: i32,
y: i32,
},Special(String),
}
和struct的成员访问符号.不同的是,枚举类型要想访问其成员,几乎无一例外的必须要用到模式匹配。并且, 你可以写一个Direction::West,但是你绝对不能写成Direction.West。
enum SpecialPoint {
Point(i32, i32),Special(String),
}
fn main() {
let sp = SpecialPoint::Point(0, 0);match sp {
SpecialPoint::Point(x, y) => {
println!("I'am SpecialPoint(x={}, y={})", x, y);
}
SpecialPoint::Special(why) => {
println!("I'am Special because I am {}", why);
}
}
}
str,String, OsStr, CStr,CString
Rust中的字符串实际上是被编码成UTF-8的一个Unicode字符数组。这么说比较拗口,简单来说,Rust字符串内部存储的是一个UTF-8数组,但是这个数组是Unicode字符经过编码得来的。因此,可以看成Rust原生就支持Unicode字符集
所有的用""包裹起来的字符串,都被声明成了一个不可变,静态的字符串。
let x = "Hello";let x:&'static str = "Hello";
一种在堆上声明的字符串String被设计了出来。 它能动态的去增长或者缩减
从str中转换:
let x:&'static str = "hello";let mut y:String = x.to_string();
println!("{}", y);
y.push_str(", world");
println!("{}", y);
一个String重新变成&str呢,用 &* 符号
fn use_str(s: &str) {
println!("I am: {}", s);
}
fn main() {
let s = "Hello".to_string();use_str(&*s);
}
首先呢, &是两个符号&和的集合,按照Rust的运算顺序,先对String进行Deref,也就是操作。 由于String实现了impl Deref
需要知道的是,将String转换成&str是非常轻松的几乎没有任何开销的。但是反过来,将&str转换成String是需要在堆上请求内存的,因此,要慎重。
let x = "hello".to_string();
x[1]; //编译错误!
Rust的字符串实际上是不支持通过下标访问的,但是呢,我们可以通过将其转变成数组的方式访问
let x = "哎哟我去".to_string();for i in x.as_bytes() {
print!("{} ", i);
}
println!("");for i in x.chars() {
print!("{}", i);
}
x.chars().nth(2);
对字符串切片是一件非常危险的事,虽然Rust支持,但是我并不推荐。因为Rust的字符串Slice实际上是切的bytes。这也就造成了一个严重后果,如果你切片的位置正好是一个Unicode字符的内部,Rust会发生Runtime的panic,导致整个程序崩溃。 因为这个操作是如此的危险,所以我就不演示了…
fn main() {
let s = format!("{1}是个有着{0:>0width$}KG重,{height:?}cm高的大胖子",81, "wayslog", width=4, height=178);// 我被逼的牺牲了自己了……
print!("{}", s);
}
函数参数声明不能省略类型
函数作为参数
fn main() {
let xm = "xiaoming";let xh = "xiaohong";say_what(xm, hi);say_what(xh, hello);
}
fn hi(name: &str) {
println!("Hi, {}.", name);
}
fn hello(name: &str) {
println!("Hello, {}.", name);
}
fn say_what(name: &str, func: fn(&str)) {
func(name)
}
main函数的返回值是(),在rust中,当一个函数返回()时,可以省略。
main函数的返回值类型是(),它是一个特殊的元组——没有元素的元组,称为unit,它表示一个函数没有任何信息需要返回。
retrun关键字用来提前返回
rust不支持返回多个值,但是可以利用元组来返回多个值
发散函数不返回,它使用感叹号!作为返回类型表示:
fn main() {
println!("hello");diverging();
println!("world");
}
fn diverging() -> ! {
panic!("This function will never return");
}
rust是一个基于表达式的语言,不过它也有语句。rust只有两种语句:声明语句和表达式语句,其他的都是表达式。基于表达式是函数式语言的一个重要特征,表达式总是返回值。
rust的声明语句可以分为两种,一种为变量声明语句,另一种为Item声明语句。变量声明语句。主要是指let语句,Item声明。是指函数(function)、结构体(structure)、类型别名(type)、静态变量(static)、特质(trait)、实现(implementation)或模块(module)的声明。这些声明可以嵌套在任意块(block)中。
表达式语句,由一个表达式和一个分号组成,即在表达式后面加一个分号就将一个表达式转变为了一个语句。所以,有多少种表达式,就有多少种表达式语句。
通常不使用一个元素的元组,不过如果你坚持的话,rust也是允许的,不过需要在元素后加一个逗号:
(0,); // single-element tuple
(0); // zero in parentheses
结构体表达式一般用于构造一个结构体对象,它除了以上从零构建的形式外,还可以在另一个对象的基础上进行构建:
let base = Point3d {x: 1, y: 2, z: 3};Point3d {y: 0, z: 10, .. base};
如果以语句结尾,则块表达式的值为():
let x: () = { println!("Hello."); };
范围表达式(range expression): 可以使用范围操作符…来构建范围对象(variant of std::ops::Range):
1..2; // std::ops::Range3..; // std::ops::RangeFrom..4; // std::ops::RangeTo..; // std::ops::RangeFull
关键字fn可以用来定义函数。除此以外,它还用来构造函数类型。与函数定义主要的不同是,构造函数类型不需要函数名、参数名和函数体。
在rust中,函数的所有权是不能转移的,我们给函数类型的变量赋值时,赋给的一般是函数的指针,所以rust中的函数类型,就像是C/C++中的函数指针,当然,rust的函数类型更安全。可见,rust的函数类型,其实应该是属于指针类型(Pointer Type)。rust的Pointer Type有两种,一种为引用(Reference&),另一种为原始指针(Raw pointer *),详细内容请看Rust Reference 8.18 Pointer Types。而rust的函数类型应是引用类型,因为它是安全的,而原始指针则是不安全的,要使用原始指针,必须使用unsafe关键字声明。
在函数内定义函数在很多其他语言中是不支持的,不过rust支持,这也是rust灵活和强大的一个体现。不过,在函数中定义的函数,不能包含函数中(环境中)的变量,若要包含,应该闭包
match所罗列的匹配,必须穷举出其所有可能。当然,你也可以用 _ 这个符号来代表其余的所有可能性情况,就类似于switch中的default语句。
match的每一个分支都必须是一个表达式,并且,除非一个分支一定会触发panic,这些分支的所有表达式的最终返回值类型必须相同。
match还有一个非常重要的作用就是对现有的数据结构进行解构,轻易的可以拿出其中的数据部分来。
一个模式中如果出现了和前面的同名变量,则这个变量会在当前作用域里被模式中的值覆盖掉。
let x = 1;let c = 'c';match c {
x => println!("x: {} c: {}", x, c),
}
println!("x: {}", x);
有的时候我们其实只对某些字段感兴趣,就可以用…来省略其他字段。
struct Point {
x: i64,
y: i64,
}
let point = Point { x: 0, y: 0 };match point {
Point { y, .. } => println!("y is {}", y),
}
我们遇到了两种不同的模式忽略的情况——_和…。这里要注意,模式匹配中被忽略的字段是不会被move的,而且实现Copy的也会优先被Copy而不是被move。
我们遇到了两种不同的模式忽略的情况——_和…。这里要注意,模式匹配中被忽略的字段是不会被move的,而且实现Copy的也会优先被Copy而不是被move。
let tuple: (u32, String) = (5, String::from("five"));let (x, s) = tuple;// 以下行将导致编译错误,因为String类型并未实现Copy, 所以tuple被整体move掉了。// println!("Tuple is: {:?}", tuple);let tuple = (5, String::from("five"));// 忽略String类型,而u32实现了Copy,则tuple不会被movelet (x, _) = tuple;
println!("Tuple is: {:?}", tuple);
在模式匹配中,当我想要匹配一个数字(字符)范围的时候,我们可以用…来表示:
let x = 1;match x {
1 ... 10 => println!("一到十"),_ => println!("其它"),
}
let c = 'w';match c {
'a' ... 'z' => println!("小写字母"),'A' ... 'Z' => println!("大写字母"),_ => println!("其他字符"),
}
当我们只是单纯的想要匹配多种情况的时候,可以使用 | 来分隔多个匹配条件
let x = 1;match x {
1 | 2 => println!("一或二"),_ => println!("其他"),
}
前面我们了解到,当被模式匹配命中的时候,未实现Copy的类型会被默认的move掉,因此,原owner就不再持有其所有权。但是有些时候,我们只想要从拿到一个变量的(可变)引用,而不想将其move出作用域,怎么做呢?答:用ref或者ref mut。
let mut x = 5;match x {
ref mut mr => println!("mut ref :{}", mr),
}
// 当然了……在let表达式里也能用let ref mut mrx = x;
在模式匹配的过程内部,我们可以用@来绑定一个变量名,这在复杂的模式匹配中是再方便不过的
#[derive(Debug)]struct Person {
name: Option,
}
let name = "Steve".to_string();let x: Option = Some(Person { name: Some(name) });match x {
Some(Person { name: ref a @ Some(_), .. }) => println!("{:?}", a),_ => {}
}
一个后置的if表达式可以被放在match的模式之后,被称为match guards。例如如下代码:
let x = 4;let y = false;match x {
4 | 5 if y => println!("yes"),_ => println!("no"),
}
泛型的trait约束
多trait约束
where关键字
trait的默认方法
trait的默认方法
derive属性
trait对象在Rust中是指使用指针封装了的 trait,比如 &SomeTrait 和 Box。
并不是所有的trait都能作为trait对象使用的
let v = vec![1, 2, 3];let o = &v as &Clone;
虽然Clone本身集成了Sized这个trait,但是它的方法fn clone(&self) -> Self和fn clone_from(&mut self, source: &Self) { … }含有Self类型,而在使用trait对象方法的时候Rust是动态派发的,我们根本不知道这个trait对象的实际类型,它可以是任何一个实现了该trait的类型的值,所以Self在这里的大小不是Self: Sized的,这样的情况在Rust中被称为object-unsafe或者not object-safe,这样的trait是不能成为trait对象的。
总结:
如果一个trait方法是object safe的,它需要满足:
方法有Self: Sized约束, 或者
同时满足以下所有条件:
没有泛型参数
不是静态函数
除了self之外的其它参数和返回值不能使用Self类型
如果一个trait是object-safe的,它需要满足:
所有的方法都是object-safe的,并且
trait 不要求 Self: Sized 约束
Rust并不会像其他语言一样可以为变量默认初始化值,Rust明确规定变量的初始值必须由程序员自己决定。
通俗的讲,let关键字可以把一个标识符和一段内存区域做“绑定”,绑定后,这段内存就被这个标识符所拥有,这个标识符也成为这段内存的唯一所有者。
{
let a: String = String::from("xyz");let b = a;
println!("{}", a);
}
let a: i32 = 100;let b = a;
println!("{}", a);
上面的代码无法编译通过,因为所有权发生了move,而下面的代码正常,是因为i32实现了Copy属性,所以在move的时候发生了copy,并绑定了新变量
在Rust中,基本数据类型(Primitive Types)均实现了Copy特性,包括i8, i16, i32, i64, usize, u8, u16, u32, u64, f32, f64, (), bool, char等等。
let mut a: &str = "abc"; //可变绑定, a <=> 内存区域A("abc")
a = "xyz"; //绑定到另一内存区域, a <=> 内存区域B("xyz")
println!("{:?}", a); //打印:"xyz"
Rust中也有const常量,常量不存在“绑定”之说,和其他语言的常量含义相同:
const PI:f32 = 3.14;
哪些情况下我们自定义的类型(如某个Struct等)可以实现Copy特性? 只要这种类型的属性类型都实现了Copy特性,那么这个类型就可以实现Copy特性。 例如:
struct Foo { //可实现Copy特性
a: i32,
b: bool,
}
struct Bar { //不可实现Copy特性
l: Vec,
}
通过derive让Rust编译器自动实现
手动实现Clone和Copy trait
去掉move后,包体内就会对x进行了可变借用,而不是“剥夺”x的所有权
借用与引用是一种相辅相成的关系,若B是对A的引用,也可称之为B借用了A。 很相近对吧,但是借用一词本意为要归还。所以在Rust用引用时,一定要注意应该在何处何时正确的“归回”借用/引用。
规则
同一时刻,最多只有一个可变借用(&mut T),或者2。
同一时刻,可有0个或多个不可变借用(&T)但不能有任何可变借用。
借用在离开作用域后释放。
在可变借用释放前不可访问源变量。
总结
借用不改变内存的所有者(Owner),借用只是对源内存的临时引用。
在借用周期内,借用方可以读写这块内存,所有者被禁止读写内存;且所有者保证在有“借用”存在的情况下,不会释放或转移内存。
失去所有权的变量不可以被借用(访问)。
在租借期内,内存所有者保证不会释放/转移/可变租借这块内存,但如果是在非可变租借的情况下,所有者是允许继续非可变租借出去的。
借用周期满后,所有者收回读写权限
借用周期小于被借用者(所有者)的生命周期。
除非编译器无法自动推导出Lifetime,否则不建议显示指定Lifetime标识符,会降低程序的可读性。
显式Lifetime
fn foo<'a>(x: &'a str, y: &'a str) -> &'a str {
if true {
x
} else {
y
}
}
Lifetime推导
要推导Lifetime是否合法,先明确两点:
输出值(也称为返回值)依赖哪些输入值
输入值的Lifetime大于或等于输出值的Lifetime (准确来说:子集,而不是大于或等于)
Lifetime推导公式:
当输出值R依赖输入值X Y Z …,当且仅当输出值的Lifetime为所有输入值的Lifetime交集的子集时,生命周期合法。
Lifetime(R) ⊆ ( Lifetime(X) ∩ Lifetime(Y) ∩ Lifetime(Z) ∩ Lifetime(...) )
fn foo<'a, 'b: 'a>(x: &'a str, y: &'b str) -> &'a str {
if true {
x
} else {
y
}
}
推导总结
通过上面的学习相信大家可以很轻松完成Lifetime的推导,总之,记住两点: 1. 输出值依赖哪些输入值。 2. 推导公式。
Lifetime in struct
struct Person<'a> {
age: &'a u8,
}
impl<'a> Person<'a> {
fn print_age(&self) {
println!("Person.age = {}", self.age);
}
}
这样加上<'a>后就可以了。读者可能会疑问为什么print_age中不需要加上’a,这是个好问题,因为print_age的输出参数为(),也就是可以不依赖任何输入参数, 所以编译器此时可以不必关心和推导Lifetime。即使是fn print_age(&self, other_age: &i32) {…}也可以编译通过。
impl<'a, 'b> Person<'a> {
fn get_max_age(&'a self, p: &'a Person) -> &'a u8 {
if self.age > p.age {
self.age
} else {
p.age
}
}
}
类似之前的Lifetime推导章节,当返回值(借用)依赖多个输入值时,需显示声明Lifetime。和函数Lifetime同理。
可以看到,第一句就已经说明了什么是闭包:闭包是引用了自由变量的函数。所以,闭包是一种特殊的函数。
在rust中,函数和闭包都是实现了Fn、FnMut或FnOnce特质(trait)的类型。任何实现了这三种特质其中一种的类型的对象,都是 可调用对象 ,都能像函数和闭包一样通过这样name()的形式调用,()在rust中是一个操作符,操作符在rust中是可以重载的。rust的操作符重载是通过实现相应的trait来实现,而()操作符的相应trait就是Fn、FnMut和FnOnce,所以,任何实现了这三个trait中的一种的类型,其实就是重载了()操作符。
let plus_one = |x: i32| x + 1;
assert_eq!(2, plus_one(1));
捕获变量
let num = 5;let plus_num = |x: i32| x + num;
assert_eq!(10, plus_num(5));
这个闭包,plus_num,引用了它作用域中的let绑定:num。更明确的说,它借用了绑定。
如果你的闭包需要它,Rust会取得所有权并移动环境:
let nums = vec![1, 2, 3];let takes_nums = || nums;
println!("{:?}", nums);
Vec拥有它内容的所有权,而且由于这个原因,当我们在闭包中引用它时,我们必须取得nums的所有权。这与我们传递nums给一个取得它所有权的函数一样。