rus语法
打印
#[derive(Debug)] // 有了这才能通过 {:?} {:#?} debug
struct Complex {
real: f64,
imag: f64,
}
impl fmt::Display for Complex {
// 默认泛型容器没有实现,要手动实现才能使用 {} 打印
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "x: {}, y: {}", self.real, self.imag)
}
}
/// this is doc comment
/// ...
fn print() {
/* format!();print!();println!();eprint!();eprintln!(); */
let variable = 1;
println!("{} 为占位符", variable);
println!(
"named argument: {subject} {verb} {object}",
subject = "A",
verb = "hava",
object = "ball"
);
println!("Base 10: {}", 69420); //69420
println!("Base 2 (binary): {:b}", 69420); //10000111100101100
println!("Base 8 (octal): {:o}", 69420); //207454
println!("Base 16 (hexadecimal): {:x}", 69420); //10f2c
println!("Base 16 (hexadecimal): {:X}", 69420); //10F2C
println!(
"打印详细信息:{o} {o:?} {o:#?}",
o = Complex {
real: 1.1,
imag: 3.2
}
); // 默认不可打印,实现 Debug 接口才可
}
// 可打印列表
struct List(Vec<i32>);
impl fmt::Display for List {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let vec = &self.0;
write!(f, "[")?; // ? 用于处理 Result 返回值,出错则直接结束函数返回error,否则继续
for (i, ele) in vec.iter().enumerate() {
if i != 0 {
write!(f, ",")?;
}
write!(f, "{} : {}", i, ele)?;
}
write!(f, "]")
}
}
更多格式见:https://doc.rust-lang.org/std/fmt/
基本
操作符类似C
fn main() {
// Integer addition
println!("1 + 2 = {}", 1u32 + 2);
// Integer subtraction
println!("1 - 2 = {}", 1i32 - 2);
// TODO ^ Try changing `1i32` to `1u32` to see why the type is important
// Short-circuiting boolean logic
println!("true AND false is {}", true && false);
println!("true OR false is {}", true || false);
println!("NOT true is {}", !true);
// Bitwise operations
println!("0011 AND 0101 is {:04b}", 0b0011u32 & 0b0101);
println!("0011 OR 0101 is {:04b}", 0b0011u32 | 0b0101);
println!("0011 XOR 0101 is {:04b}", 0b0011u32 ^ 0b0101);
println!("1 << 5 is {}", 1u32 << 5);
println!("0x80 >> 2 is 0x{:x}", 0x80u32 >> 2);
// Use underscores to improve readability!
println!("One million is written as {}", 1_000_000u32);
}
tuple
fn fun() {
let tuple_of_tuples = ((1u8, 2u16, 2u32), (4u64, -1i8), -2i16);
println!("tuple of tuples: {:?}", tuple_of_tuples);
let matrix = Matrix(1.1f32, 1.2f32, 1.3, 1.4);
println!("Matrix:\n{}", matrix);
println!("Transpose:\n{}", transpose(matrix));
}
struct Matrix(f32, f32, f32, f32);
impl fmt::Display for Matrix {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "( {} {} )\n( {} {})", self.0, self.1, self.2, self.3)
}
}
fn transpose(matrix: Matrix) -> Matrix {
let Matrix(e0, e1, e2, e3) = matrix; // Destructuring
Matrix(e0, e1, e3, e2)
}
切片:占用两个字,一个为指向数据的指针,另一个为长度。
fn analyze_slice(slice: &[i32]) {
println!("first element of the slice: {}", slice[0]);
println!("the slice has {} elements", slice.len());
}
fn fun() {
let arr1: [i32; 5] = [1,2,3,4,5];
let arr2: [i32; 10] = [0; 10];
println!("{} {} {}", arr1[0], arr2.len(), std::mem::size_of_val(&arr1));
// 数组被自动借用为切片,空切片:&[]
analyze_slice(&arr1);
analyze_slice(&arr2[3 .. 6]);
// 使用 get 方法得到被 Option 包裹的数据,可以使用 match 或 expect 方法处理
for i in 0..arr2.len() + 1 {
match arr2.get(i) {
Some(val) => println!("{}: {}", i, val),
None => println!("over the bound!"),
}
}
}
结构体 枚举类型 别名 静态变量
结构体
// 无字段可用于泛型
struct Unit;
// struct tuple 即命名数组
struct Pair(i32, i32);
#[derive(Debug)]
struct Point {
x: f32,
y: f32,
}
struct Rectangle {
top_left: Point,
bottom_right: Point,
}
impl Rectangle {
fn area(&self) -> f32 {
(self.bottom_right.x - self.top_left.x) * (self.bottom_right.y - self.top_left.y)
}
}
fn square(p: Point, l: f32) -> Rectangle {
Rectangle {
bottom_right: Point { // 注意这里要在 top_left 之前声明,否则p被 borrowed ,就无法再使用
x: p.x + l,
y: p.y + l,
},
top_left: p,
}
}
fn fun() {
let point1 = Point{x:1.1, y:1.2};
// 使用结构体更新语法引用其他结构体数据
let point2 = Point{x:2.1, ..point1};
// destructure
let Point { x: left_edge, y: top_edge } = point2;
let _rec = Rectangle {
top_left: Point { x: left_edge, y: top_edge },
bottom_right: point1, // point1 be borrowed
};
}
枚举类型,use 与命名空间
enum Event {
PageLoad, // unit struct
KeyPress(char), // struct tuple
Paste(String),
Click { x: i64, y: i64 }, // struct
}
fn inspect(event: Event) {
// 使用 use 后无需使用命名空间
use crate::Event::{KeyPress, PageLoad}; // use create::Event::*;
match event {
PageLoad => println!("page loaded"),
KeyPress(c) => println!("pressed '{}'.", c),
Event::Paste(s) => println!("pasted \"{}\".", s),
Event::Click { x, y } => {
println!("clicked at x={}, y={}.", x, y);
}
}
}
#![allow(dead_code)]
use std::fmt::Display;
use crate::List::*;
enum List<T: Display> {
Cons(T, Box<List<T>>),
Nil,
}
impl<T: Display> List<T> {
fn new() -> List<T> {
List::Nil
}
// self 代表对象,所有权会移交
fn prepend(self, elem: T) -> List<T> {
List::Cons(elem, Box::new(self))
}
// &self 代表引用,所有权不会移交
fn len(&self) -> u64 {
match self {
List::Cons(_, tail) => 1 + tail.len(),
Nil => 0,
}
}
fn stringify(&self) -> String {
match self {
List::Cons(head, tail) => {
format!("{}, {}", head, tail.stringify())
}
List::Nil => {
format!("Nil")
}
}
}
}
fn main() {
let mut list = List::<i32>::new();
list = list.prepend(1);
list = list.prepend(2);
list = list.prepend(3);
println!("{}", list.stringify()); // 3, 2, 1, Nil
}
enum 类C用法
// enum with implicit discriminator (starts at 0)
enum Number {
Zero,
One,
Two,
}
// enum with explicit discriminator
enum Color {
Red = 0xff0000,
Green = 0x00ff00,
Blue = 0x0000ff,
}
fn main() {
// `enums` can be cast as integers.
println!("zero is {}", Number::Zero as i32);
println!("one is {}", Number::One as i32);
println!("roses are #{:06x}", Color::Red as i32);
println!("violets are #{:06x}", Color::Blue as i32);
}
别名
type AliasType = SomeType;
静态变量
- const:不可变,常用
- static:可能是 mut 类型,被推断为静态生命周期 'static,访问修改都是 unsafe 的
类型转换
使用 use 显现类型转换,rust 没有隐式类型转换
let decimal = 65.4321_f32;
let integer = decimal as u8;
let char = integer as char;
// 300.0 as u8 is 255
println!(" 300.0 as u8 is : {}", 300.0_f32 as u8);
// -100.0 as u8 is 0
println!("-100.0 as u8 is : {}", -100.0_f32 as u8);
// nan as u8 is 0
println!(" nan as u8 is : {}", f32::NAN as u8);
内置类型转换 From Into
let my_str = "hello";
let my_string = String::from(my_str);
struct Number {
value: i32,
}
impl From<i32> for Number {
fn from(item: i32) -> Self {
Number { value: item }
}
}
let num = Number::from(30);
let int = 5;
let num: Number = int.into();
try_from / try_into 返回一个 Result
#[derive(Debug, PartialEq)]
struct EvenNumber(i32);
impl TryFrom<i32> for EvenNumber {
type Error = ();
fn try_from(value: i32) -> Result<Self, Self::Error> {
if value % 2 == 0 {
Ok(EvenNumber(value))
} else {
Err(())
}
}
}
使用 Display 转为 String
使用字符串实现的 FromStr 接口的 parse 方法将 String 转为其他类型
let parsed: i32 = "5".parse().unwrap();
let turbo_parsed = "10".parse::<i32>().unwrap();
控制流
let x = if n < 100.0 {
1
} else if n < 10.0 {
2
} else {
3
};
// loop 死循环 可搭配 break continue
fn main() {
'outer: loop {
println!("Entered the outer loop");
'inner: loop {
println!("Entered the inner loop");
// This breaks the outer loop
break 'outer;
}
println!("This point will never be reached");
}
println!("Exited the outer loop");
}
/*
Entered the outer loop
Entered the inner loop
Exited the outer loop
*/
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
while 搭配布尔语句
for n in 1..101 {
}
for n in 1..=100 {
}
let names = vec!["Bob", "Frank", "Ferris"];
// 元素被借用,循环后可再用
for name in names.iter() {
match name {
&"Ferris" => println!("There is a rustacean among us!"),
_ => println!("Hello {}", name),
}
}
// 元素可以被原地修改,循环后可用
for name in names.iter_mut() {
*name = match name {
&mut "Ferris" => "There is a rustacean among us!",
_ => "Hello",
}
}
// 元素被移动,循环后不可再用
for name in names.into_iter() {
match name {
"Ferris" => println!("There is a rustacean among us!"),
_ => println!("Hello {}", name),
}
}
match destructuring
match number {
1 => println!("One!"),
2 | 3 | 5 | 7 | 11 => println!("This is a prime"),
13..=19 => println!("A teen"),
_ => println!("Ain't special"), // 处理其他情况,不可缺少
}
let arr = 数组或切片;
match arr {
[0, second, third] => print!(),
[1, _, third] => print!(),
// 绑定一些忽略一些
[2, second, ..] => print!(),
// 将剩余的绑定到数组或切片
[3, second, tail @ ..] => print!(),
[first, middle @ .., last] => print!(),
}
match tuple {
(0, x, y) if x > 0 => println!(""), // 分支守卫
(3, .., 4) => println!(""),
_ => print!(),
}
let reference = &4;
match reference {
&val => print!("这里变量被解引用"),
}
match value {
ref r => print!("创建新引用"),
}
match value {
ref mut m => print!("可变引用"),
}
match value {
Foo { x: 1, y: a} => print!(""),
Foo { x: a, y: 1} => print!(""),
}
// 使用 @ 绑定对象
match age() {
0 => xx,
n @ 1 ..= 12 => xx,
n @ 13 ..= 19 => xx,
n => xx,
}
闭包
fn main() {
/****************** borrow / reference *******************/
let mut outer_var = 42;
let closure_annotated = |i: i32| -> i32 { i + outer_var }; // immutrably borrow
let mut closure_inferred = |i| outer_var += i; // mutably borrow
closure_inferred(1); // 有了这句使用后才能推断出闭包类型
// println!("{}", outer_var); // 在其他借用没有结束时借用,会报错
closure_inferred(1);
println!("{}", outer_var); // 此处不会报错
/****************** move *******************/
let movable = Box::new(3);
let consume = || {
println!("`movable`: {:?}", movable);
std::mem::drop(movable); // drop函数需要传入move值,此处发生 move
};
consume();
// consume(); 因为变量只能move一次,所以再次调用会报错
let contains = move |needle| haystack.contains(needle); // 在 | 前使用 move 关键字,强制move
}
当闭包作为函数参数时,需要用接口指明 参数为闭包 还有 闭包的参数捕获方式:Fn
FnMut
FnOnce
。权限逐渐变严格,FnOnce接收更宽松的两类接口,FnMut也可传入Fn接口
fn apply<F>(f: F)
where
F: FnOnce(),
{
f();
}
fn main() {
use std::mem;
let mut w = "hello".to_owned(); // create owned data
let f = || {
mem::drop(w);
};
apply(f); // 函数f的参数是 move 来的,apply中必须要用 FnOnce
}
当闭包作为输出时要用 impl 和接口
指明类型,同时还要用 move
将捕获变量转移出,否者在函数结束后引用变量销毁。
fn create_fn() -> impl Fn() {
let text = "Fn".to_owned();
move || println!("This is a: {}", text)
}
fn create_fnmut() -> impl FnMut() {
let text = "FnMut".to_owned();
move || println!("This is a: {}", text)
}
fn create_fnonce() -> impl FnOnce() {
let text = "FnOnce".to_owned();
move || println!("This is a: {}", text)
}
fn main() {
let fn_plain = create_fn();
let mut fn_mut = create_fnmut();
let fn_once = create_fnonce();
fn_plain();
fn_mut();
fn_once();
}
函数式编程
// Functional approach 所有奇数的平方
let sum_of_squared_odd_numbers: u32 =
(0..).map(|n| n * n) // All natural numbers squared
.take_while(|&n_squared| n_squared < upper) // Below upper limit
.filter(|&n_squared| is_odd(n_squared)) // That are odd
.sum(); // Sum them
println!("functional style: {}", sum_of_squared_odd_numbers);
Diverging functions
不会正常返回的函数
fn foo() -> ! {
panic!("This call never returns.");
}
#![feature(never_type)]
fn main() {
let x: ! = panic!("This call never returns.");
println!("You will never see this line!");
}
fn main() {
fn sum_odd_numbers(up_to: u32) -> u32 {
let mut acc = 0;
for i in 0..up_to {
// Notice that the return type of this match expression must be u32
// because of the type of the "addition" variable.
let addition: u32 = match i%2 == 1 {
// The "i" variable is of type u32, which is perfectly fine.
true => i,
// On the other hand, the "continue" expression does not return
// u32, but it is still fine, because it never returns and therefore
// does not violate the type requirements of the match expression.
false => continue,
};
acc += addition;
}
acc
}
println!("Sum of odd numbers up to 9 (excluding): {}", sum_odd_numbers(9));
}
模块
模块默认都是私有的,使用 pub 关键字修饰后其他模块才可见。
结构体的字段与方法也都默认私有。
mod my_mod {
fn private_fun(){}
pub fn public_fun(){}
pub mod nested {
pub(in crate::my_mod) fn public_in_given_mod(){}
pub(super) fn public_in_super_super_mod(){}
pub(crate) fn same_as_private(){}
}
}
use 声明
use crate::deeply::nested::{
my_first_function,
my_second_function,
AndATraitType
};
use deeply::nested::function as other_function;
super & self
mod mod1 {
pub(super) fn fun1() {
println!("fun1");
}
}
mod mod2 {
mod mod3 {
pub fn fun2() {
println!("fun2");
}
}
fn fun() {
self::mod3::fun2();
super::mod1::fun1();
}
}
$ tree .
.
├── my
│ ├── inaccessible.rs my::nested::xxx
│ └── nested.rs my::nested::xxx
├── my.rs my::xxx
└── split.rs
在 split.rs 文件头部输入 mod my; 使 my.rs 内部自动创建模块my并插入 my.rs 的内容
在 my.rs 文件头部插入 mod inaccessible; 和 pub mod nested;
|--main.rs 通过 pub mod util; 引入util模块
|--util.rs
|--util/
|--mod.rs
|--math.rs
注意:utils.rs 和 util/mod.rs 内容都是util模块下的内容;二者同时存在会冲突
Cargo & Crates
crates.io: Rust Package Registry https://crates.io
cargo new foo # A binary
cargo new --lib bar # A library
cargo build
cargo run
.
├── bar
│ ├── Cargo.toml
│ └── src
│ └── lib.rs # 库
│ └── main.rs # 可执行文件
└── foo
├── Cargo.toml
└── src
└── lib.rs # 库
└── main.rs # 可执行文件
一个 crate 包中生成多个可执行文件
foo
├── Cargo.toml
└── src
├── main.rs
└── bin
└── my_other_bin.rs
# cargo build 生成默认文件
# cargo build --bin my_other_bin 指定可执行文件,文件名为 my_other_bin
Attributes
修饰crate、条件编译、格式化、链接、单元测试、压力测试、类宏属性
#![allow(dead_code)] // #! 开头,修饰crate
// 三种修饰 mod fn 的格式
#[attribute = "value"]
#[attribute(key = "value")]
#[attribute(value)]
cfg 代指 configure,在使用 rustc 编译时可以通过 --cfg 指定
#[cfg(...)]
条件编译cfg!(...)
在运行时返回布尔值
#[cfg(target_os = "linux")]
fn are_you_on_linux() {
println!("You are running linux!");
}
#[cfg(not(target_os = "linux"))]
fn are_you_on_linux() {
println!("You are *not* running linux!");
}
fn main() {
are_you_on_linux();
println!("Are you sure?");
if cfg!(target_os = "linux") {
println!("Yes. It's definitely linux!");
} else {
println!("Yes. It's definitely *not* linux!");
}
}
泛型
接口约束也可以是空接口
struct Empty;
struct Null;
/* impl Display for Empty, Null */
trait Drop<T> {
fn drop(self, _: T);
}
impl<T: Display, U> Drop for U // 此处 T 被加上了 Display 接口的约束
where // where 用于让接口约束更美观
U: Display + Debug, // 组合多个不同接口
{
fn drop(self, _: T) {} // 此处 self 和参数都 move 了过来,函数返回时变量死亡
}
fn main() {
let empty = Empty;
let null = Null;
empty.drop(null); // empty 和 null 都move到方法内,之后死亡
}
不得不用 where 进行约束的场景:
impl<T> PrintInOption for T
where
Option<T>: Debug // 因为用于打印的是 Option<T>,所以无法通过正常语法约束T
{
fn print_in_option(self) {
println!("{:?}", Some(self));
}
}
使用类型关联使得容器中某种些变量的类型由其他变量决定
struct Container(i32, i32);
trait Contains {
// Define generic types here which methods will be able to utilize.
type A;
type B;
fn contains(&self, _: &Self::A, _: &Self::B) -> bool;
fn first(&self) -> i32;
fn last(&self) -> i32;
}
impl Contains for Container {
type A = i32;
type B = i32;
fn contains(&self, number_1: &Self::A, number_2: &Self::B) -> bool {
(&self.0 == number_1) && (&self.1 == number_2)
}
fn first(&self) -> i32 { self.0 }
fn last(&self) -> i32 { self.1 }
}
// 不使用类型绑定的写法:fn difference<A, B, C>(container: &C) -> i32 where C: Contains<A, B> { ... }
fn difference<C: Contains>(container: &C) -> i32 {
container.last() - container.first()
}
作用域规则
struct ToDrop;
// 析构函数,非必须,销毁时自动调用
impl Drop for ToDrop {
fn drop(&mut self) {
println!("ToDrop is being dropped");
}
}
fn main() {
let x = ToDrop;
println!("Made a ToDrop!");
}
所有权
所有资源只有一个拥有者,但不是所有变量都有资源。例如 let x = y
foo(x)
资源所有权发生转移,即 move,解决了悬挂指针的问题。
在 move 时 Mutability 可以改变
fn destory_box(c: Box<i32>) {
println!("Destroying a box contains {}", c);
}
fn main() {
let x = 5u32; // stack alloc
let y = x; // u32 have Copy trait
println!("{} {}", x, y); // 引用
let a = Box::new(5i32);
let b = a; // move
destory_box(b);
}
let Person {
name, // move
ref age // borrow
} = Person {
name: String::from("Alice"),
age: Box::new(20),
};
借用
函数传参时通过 &T
得到参数的引用,&mut T
为可变引用。
- 在不可变引用使用期间,元对象不可被可变引用
- 同一时间段只能有一个可变引用
- 在最后一个可变引用出现的语句之后,此可变引用生命结束
fn main() {
let mut i = 1;
let i1 = &i;
let ref i11 = i; // 使用 ref 声明引用
let i2 = &mut i;
// let i3 = &mut i; // 此处会报错,因为多个可变引用冲突
// i = 2; // 此处报错,因为i被借用
println!("{}", i2); // 可变引用i2最后一次出现
let i3 = &mut i;
println!("{}", i3);
// println!("{}", i1); // 如果有这个语句,前面两个可变引用也出错
}
// 在 destructuring 中使用ref指定部分字段为引用
let Point {
x: ref ref_to_x,
y: _
} = point;
生命周期
// Lifetimes are annotated below with lines denoting the creation
// and destruction of each variable.
// `i` has the longest lifetime because its scope entirely encloses
// both `borrow1` and `borrow2`. The duration of `borrow1` compared
// to `borrow2` is irrelevant since they are disjoint.
fn main() {
let i = 3; // Lifetime for `i` starts. ────────────────┐
// │
{ // │
let borrow1 = &i; // `borrow1` lifetime starts. ──┐│
// ││
println!("borrow1: {}", borrow1); // ││
} // `borrow1` ends. ─────────────────────────────────┘│
// │
// │
{ // │
let borrow2 = &i; // `borrow2` lifetime starts. ──┐│
// ││
println!("borrow2: {}", borrow2); // ││
} // `borrow2` ends. ─────────────────────────────────┘│
// │
} // Lifetime ends. ─────────────────────────────────────┘
有生命周期显式注释的函数签名
在函数中使用泛型和符号 'a
'b
'c
... 限制引用的生命周期
// `print_refs` takes two references to `i32` which have different
// lifetimes `'a` and `'b`. These two lifetimes must both be at
// least as long as the function `print_refs`.
fn print_refs<'a, 'b>(x: &'a i32, y: &'b i32) {
println!("x is {} and y is {}", x, y);
}
// A function which takes no arguments, but has a lifetime parameter `'a`.
fn failed_borrow<'a>() {
let _x = 12;
// ERROR: `_x` does not live long enough
let y: &'a i32 = &_x;
// Attempting to use the lifetime `'a` as an explicit type annotation
// inside the function will fail because the lifetime of `&_x` is shorter
// than that of `y`. A short lifetime cannot be coerced into a longer one.
}
fn main() {
// Create variables to be borrowed below.
let (four, nine) = (4, 9);
// Borrows (`&`) of both variables are passed into the function.
print_refs(&four, &nine);
// Any input which is borrowed must outlive the borrower.
// In other words, the lifetime of `four` and `nine` must
// be longer than that of `print_refs`.
failed_borrow();
// `failed_borrow` contains no references to force `'a` to be
// longer than the lifetime of the function, but `'a` is longer.
// Because the lifetime is never constrained, it defaults to `'static`.
}
// One input reference with lifetime `'a` which must live 输入引用的生命至少与函数一样长
// at least as long as the function.
// Mutable references are possible with lifetimes as well.
fn add_one<'a>(x: &'a mut i32) {
*x += 1;
}
// Multiple elements with different lifetimes. In this case, it
// would be fine for both to have the same lifetime `'a`, but
// in more complex cases, different lifetimes may be required.
fn print_multi<'a, 'b>(x: &'a i32, y: &'b i32) {
println!("`print_multi`: x is {}, y is {}", x, y);
}
// Returning references that have been passed in is acceptable.
// However, the correct lifetime must be returned.
fn pass_x<'a, 'b>(x: &'a i32, _: &'b i32) -> &'a i32 { x }
// 注意返回值的生命周期必须 与输入生命周期相同 或 为static
fn invalid_output<'a>() -> &'a String { &String::from("foo") }
// The above is invalid: `'a` must live longer than the function.
// Here, `&String::from("foo")` would create a `String`, followed by a
// reference. Then the data is dropped upon exiting the scope, leaving
// a reference to invalid data to be returned.
结构体和方法都可以有生命周期标注
struct NamedBorrowed<'a> {
x: &'a i32,
y: &'a i32,
}
impl<'a> Default for NamedBorrowed<'a> {
fn default() -> Self {
Self {
x: &10,
}
}
}
// method 与 function 一样,都可使用生命周期
struct Owner(i32);
impl Owner {
// Annotate lifetimes as in a standalone function.
fn add_one<'a>(&'a mut self) { self.0 += 1; }
fn print<'a>(&'a self) {
println!("`print`: {}", self.0);
}
}
泛型的生命周期约束
泛型要满足指定接口且生命周期要长于指定符号
fn print_ref<'a, T>(t: &'a T)
where
T: Debug + 'a
{
println!("`print_ref`: t is {:?}", t);
}
强制转换
生命周期存在差异时强制转换为较短的情况
// 输入变量生命周期不同,a 成为较短的那个
fn multiply<'a>(first: &'a i32, second: &'a i32) -> i32 {
first * second
}
// a 至少和 b 一样长,返回的生命周期被转换为 b
fn choose_first<'a: 'b, 'b>(first: &'a i32, _: &'b i32) -> &'b i32 {
first
}
fn main() {
let first = 2; // Longer lifetime
{
let second = 3; // Shorter lifetime
println!("The product is {}", multiply(&first, &second));
println!("{} is the first", choose_first(&first, &second));
};
}
Static 静态生命周期
'static 对应变量存储在二进制的只读空间,有2种创建方法:
- 使用
static
关键字声明 - 创建
&'static str
类型的字符串
static NUM:i32 = 1;
let static_string = "I'm in read-only memory";
在签名中可以使用 'static 约束
fn print_it( input: impl Debug + 'static ) {
println!( "'static value passed in is: {:?}", input );
}
fn main() {
// i is owned and contains no references, thus it's 'static:
let i = 5;
print_it(i);
// Oops!!! &i only has the lifetime defined by the scope of main(), so it's not 'static:
print_it(&i);
}
生命周期是一定存在的,只是大多数情况下可以省略并由编译器推断
接口
某些简单的内置接口可通过 #[derive]
实现,也可手动实现更复杂版本
- Comparison traits:
Eq
,PartialEq
,Ord
,PartialOrd
. Clone
, to create T from &T via a copy.Copy
, to give a type 'copy semantics' instead of 'move semantics'.Hash
, to compute a hash from &T.Default
, to create an empty instance of a data type.Debug
, to format a value using the {:?} formatter.
使用 dyn
返回接口
在 rust 中返回值大小需要固定大小,无法直接返回一个 trait,可以返回一个指向接口的 Box 指针。但 rust 要求尽可能的显示声明堆内存分配,所以要加上 dyn
fn fun() -> Box<dyn Display> {
Box::new(A_Struct_impl_Display{})
}
操作符重载
实现 std::ops
下的接口,如 std::ops::Add
std::ops::Sub
core::ops - Rust (rust-lang.org) doc.rust-lang.org/core/ops/
use std::ops::{Add, Mul};
#[derive(Debug)]
struct Foo;
impl Add for Foo {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
rhs
}
}
#[derive(Debug)]
struct Bar;
impl Mul<Foo> for Bar { // 注意泛型,默认 Self
type Output = Foo;
fn mul(self, rhs: Foo) -> Self::Output {
rhs
}
}
fn main() {
let foo1 = Foo{};
let foo2 = Foo{};
let foo3 = Foo{};
let bar = Bar{};
println!("{:?}", foo1 + foo2);
println!("{:?}", bar * foo3);
}
Drop 接口
struct Droppable {
}
impl Drop for Droppable {
fn drop(&mut self) {
// 销毁时调用,释放资源。
// 按照对象生成的相反顺序销毁,类似出栈
}
}
迭代器接口
struct Fibonacci {
curr: u32,
next: u32,
}
impl Fibonacci {
fn new() -> Fibonacci {
Fibonacci { curr: 1, next: 1 }
}
}
impl Iterator for Fibonacci {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
let current = self.curr;
self.curr = self.next;
self.next = self.curr + current;
Some(current)
}
}
fn main() {
let fib = Fibonacci::new();
for i in fib.skip(2).take(5) {
// for 循环中自动调用 .into_iter() 方法
println!("> {}", i); // 2 3 5 8 13
}
for i in 0..3 {}
}
traits 作为输入输出
fn f<'a>(r : &'a impl Display) -> impl Iterator + 'a {
// 直接使用 imp traits 省略泛型
}
复制
- Copy 简单的按位拷贝
- Clone 深拷贝
// 出现Copy时必须有Clone
#[derive(Debug, Clone, Copy)]
struct Unit;
// 可拷贝 Box 指针内部数据
#[derive(Clone, Debug)]
struct Pair(Box<i32>, Box<i32>);
接口组合
rust 中没有“继承”
trait Obj {}
trait Named: Obj {
fn name(&self) -> String;
}
trait Animal {
fn run(self);
}
trait Dog: Named + Animal {}
fn f(d: &dyn Dog) {
d.name();
d.run();
}
接口方法冲突
trait A {
fn fun(&self);
}
trait B {
fn fun(&self);
}
struct S;
impl A for S {
fn fun(&self) {}
}
impl B for S {
fn fun(&self) {}
}
fn main() {
let s = S {};
<S as A>::fun(&s);
<S as B>::fun(&s);
}
错误处理
panic!();
用于不可恢复错误Option
用于接收缺少值的情况Result
用于让调用者解决错误
panic
fn fun() {
panic!("严重错误");
}
条件编译
fn drink(s: &str) {
if cfg!(panic="abort"){
println!("");
} else {
println!("");
}
}
#[cfg(panic = "abort")]
fn ah(){ println!("xxx");}
#[cfg(not(panic="abort"))]
fn ah(){ println!("yyy");}
fn drink(){
ah();
}
rustc lemonade.rs -C panic=abort
指定编译条件
Option
pub enum Option<T> {
/// No value.
None,
/// Some value of type `T`.
Some(T),
}
可使用 match 分支或 unwrap 解决
使用 ?
Option变量后接上一个 ? 如果是Some则得到内部值,否则终止函数并返回 None
map map_or map_or_else 函数式编程
简化 match 分支语句
option.map(move |v| {....});
option.map_or(0, move |v| {xxx; 1}); // 为None返回0,否则执行函数处理Some内部值
option.map_or_else(f1, f2); // 都是闭包,为None和Some分别执行不同函数
and_then
Option内部如果是None 则直接返回None,否则执行传入的闭包,闭包会返回一个Option
此外还有 or() or_else() get_or_insert() get_or_insert_with()
Result
pub enum Result<T, E> {
Ok(T),
Err(E),
}
例如解析字符串失败时返回Result,同时 main 函数也可以返回 Result
use std::num::ParseIntError;
fn main() -> Result<(), ParseIntError> {
let number_str = "10";
let number = match number_str.parse::<i32>() {
Ok(number) => number,
Err(e) => return Err(e),
};
println!("{}", number);
Ok(())
}
和 Option 类似,也可使用 match 处理。也有 map or and 等简化方法。也有和Option一样的 ?
操作。
可使用 type 关键字起别名
type AliasedResult<T> = Result<T, ParseIntError>;
常用技巧有 Result 和 Option 的相互嵌套,使用Box包装Result
测试
单元测试
测试单一模块,且能够测试私有接口。
创建 tests
模块并使用 #[cfg(test)]
注解。一般测试模块与目标函数放置于同一个文件。
使用 cargo test
运行测试,cargo test name_slice
可运行指定测试
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
// 私有方法也可测试
fn bad_add(a: i32, b: i32) -> i32 {
a - b
}
#[cfg(test)]
mod tests {
// Note this useful idiom: importing names from outer (for mod tests) scope.
use super::*;
#[ignore = "这个属性能用于忽略测试"]
#[test]
fn test_add() {
assert_eq!(add(1, 2), 3);
}
// 测试函数也可以返回 Result
#[test]
fn test_bad_add() -> Result<(), String> {
if 2 == bad_add(1, 1) {
Ok(())
} else {
Err("the function is bad".to_owned())
}
}
#[test]
#[should_panic(expected="should panic")] // 当希望发生 panic 时使用
fn test_should_panic() {
panic!("should panic")
}
}
文档测试
文档注解支持commonMarkdown格式,其中的代码块在 cargo test 时也执行。
集成测试
cargo将集成测试放在与src平级的tests目录中,只能使用公开接口。
可以将所有测试都需要的初始设置放入一个函数,再引入test
例如:
// tests/common/mod.rs
pub fn setup() {
// some setup code, like creating required files/directories, starting
// servers, etc.
}
// importing common module.
mod common;
#[test]
fn test_add() {
// using common code.
common::setup();
assert_eq!(adder::add(3, 2), 5);
}
开发期依赖
引入后只在开始时有效
# standard crate data is left out
[dev-dependencies]
pretty_assertions = "1"
Vector
可变长的数组,一个vector对象有3个组成部分:指向数据的指针,长度,容量。
fn main() {
let mut v1: Vec<i32> = (0..10).collect();
let mut v2 = vec!["a", "b", "c"];
println!(
"{} {} {} {}", // 10 10 3 3
v1.len(),
v1.capacity(),
v2.len(),
v2.capacity()
);
for (_i, x) in v1.iter_mut().enumerate() {
*x *= 2;
}
v2.append(&mut vec!["d", "e", "f", "g"]);
println!(
"{} {} {:?}", // 7 7 [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
v2.len(),
v2.capacity(),
v1
);
}
字符串
Rust中有两种字符串:String 和 &str。String是存储在堆上的字节vector,是不以null结尾的utf8字符。&str是指向utf8序列的切片,指向String。
std::str 和 std::string 专门用于处理 str 和 string 类型。
fn main() {
let s: &'static str = "this is a string data"; // 字面量分配在常量区,只读
println!("{:p} : {}", s.as_ptr(), s);
for word in s.split_whitespace().rev() {
println!("{:p} : {}", word.as_ptr(), word);
}
let S = String::from("this is a string data");
let ss = s.replace("string", "String");
println!("{:p}", S.as_str());
println!("{:p}", ss.as_str());
}
// print:
// 0x7ff6dbc656e8 : this is a string data
// 0x7ff6dbc656f9 : data
// 0x7ff6dbc656f2 : string
// 0x7ff6dbc656f0 : a
// 0x7ff6dbc656ed : is
// 0x7ff6dbc656e8 : this
// 0x2699c84f380
// 0x2699c8522c0
智能指针
Box
类似c++的 unique_ptr
智能指针,rust中对象默认分配栈上。可通过 Box 使其分配到堆上,当Box离开作用域时指针和内部对象都销毁。可以通过 * 操作符解引用,直接获得内部对象。
#[derive(Debug, Clone, Copy)]
struct Point {
x: f64,
y: f64,
}
fn main() {
let p = Point { x: 1.0, y: 2.0 };
let bp = Box::new(p);
println!(
"{} {} {}", // 16 8 16
std::mem::size_of_val(&p),
std::mem::size_of_val(&bp),
std::mem::size_of_val(&*bp)
);
}
Rc
Rc(Reference Counting)是对内部对象的引用计数指针,类似c++的 shared_ptr 和 weak_ptr。
内部分为强引用和弱引用,弱引用用于防止循环引用。
fn main() {
let s = "xxx".to_string();
let s1 = Rc::new(s);
println!("{} {}", Rc::strong_count(&s1), Rc::weak_count(&s1)); // 1 0
{
let _s2 = Rc::clone(&s1);
println!("{} {}", Rc::strong_count(&s1), Rc::weak_count(&s1)); // 2 0
}
{
println!("{} {}", Rc::strong_count(&s1), Rc::weak_count(&s1)); // 1 0
let s3 = Rc::downgrade(&s1);
println!("{} {}", Rc::strong_count(&s1), Rc::weak_count(&s1)); // 1 1
let _s4 = s3.upgrade();
println!("{} {}", Rc::strong_count(&s1), Rc::weak_count(&s1)); // 2 1
}
println!("{} {}", Rc::strong_count(&s1), Rc::weak_count(&s1)); // 1 0
}
Arc
Arc(Atomically Reference Counted)用于多线程间共享所有权。
use std::{sync::Arc, thread};
fn main() {
let s = Arc::new("string");
let mut join_vec = vec![];
for _ in 0..10 {
let ss = Arc::clone(&s);
let join = thread::spawn(move || {
println!("{:?} {:?} {:p}", ss, Arc::strong_count(&ss), ss.as_ptr());
});
join_vec.push(join);
}
join_vec.into_iter().for_each(|j| {
let _ = j.join();
});
}
// "string" 5 0x7ff7f8e29f38
// "string" 6 0x7ff7f8e29f38
// "string" 9 0x7ff7f8e29f38
// "string" 11 0x7ff7f8e29f38
// "string" 10 0x7ff7f8e29f38
// "string" 10 0x7ff7f8e29f38
// "string" 9 0x7ff7f8e29f38
// "string" 9 0x7ff7f8e29f38
// "string" 8 0x7ff7f8e29f38
// "string" 8 0x7ff7f8e29f38
多线程
一个简单 map-reduce 程序
MapReduce
use std::{collections::HashMap, thread};
fn main() {
let data = "86967897737416471853297327050364959
11861322575564723963297542624962850
70856234701860851907960690014725639
38397966707106094172783238747669219
52380795257888236525459303330302837
58495327135744041048897885734297812
69920216438980873548808413720956532
16278424637452589860345374828574668";
let mut join_vec = vec![];
let chunked_data = data.split_whitespace();
for (i, segment) in chunked_data.enumerate() {
join_vec.push(thread::spawn(move || -> HashMap<u32, i32> {
let mut map = HashMap::new();
segment.chars().for_each(|c| {
let num = c.to_digit(10).expect("should be digit");
match map.get_key_value(&num) {
Some((&k, &v)) => {map.insert(k, v + 1);}
None => {map.insert(num, 1);}
}
});
println!("thread {i} get result: {:?}", map);
map
}));
}
let mut final_result = HashMap::new();
join_vec.into_iter().for_each(|res| {
match res.join() {
Ok(map) => {
map.iter().for_each(|(&k, &v)| {
match final_result.get(&k) {
Some(old_result) => {
final_result.insert(k, v + old_result);
},
None => {
final_result.insert(k, v);
},
}
});
},
Err(err) => {
println!("thread fail: {:?}", err);
},
}
});
println!("final result: {:#?}", final_result);
}
标准库的 channel 支持多sender,1个receiver。第三方库 crossbeam_channel 支持多个生产者和消费者。
use std::{sync::mpsc, thread};
static THREAD_NUM: i32 = 3;
fn main() {
let (sender, receiver) = mpsc::channel();
let mut join_vec = vec![];
for i in 0..THREAD_NUM {
let sender2 = sender.clone();
let join = thread::spawn(move || {
sender2.send(i).unwrap();
println!("thread {i} send: {i}");
});
join_vec.push(join);
}
for _ in 0..THREAD_NUM {
receiver.recv().map_or_else(
|err| println!("err: {err}"),
|recv| println!("recv: {recv}"),
);
}
for j in join_vec {
j.join().expect("the child thread panic");
}
}
IO
*nix 和 windows 内部使用不同的格式保存字符串,所以Path对应的路径表示不是uft8格式字符串,而是依赖平台的 OsStr。Path 依赖 OsStr 创建,是不可变的。PathBuf 是可变的,与 Path 相关的。关系类似String 和 str。Path 转换为字符串可能失败,但一定可转换为 OsStr。
use std::path::Path;
fn main() {
let cur_path = Path::new(".");
let mut new_path = cur_path.join("a").join("b");
new_path.push("c");
new_path.push("file.txt");
println!("{:?} {:?}", cur_path, new_path);
}
文件操作:
use std::{
fs::{self, File},
io::{self, Write, BufRead},
path::Path,
};
fn main() -> io::Result<()> {
let file_path = Path::new("D:/tmp.txt");
if file_path.try_exists()? {
fs::remove_file(file_path)?;
}
{
let mut f = File::create(file_path)?;
f.write(b"123456\nabcdef")?;
}
let file = match File::open(file_path) {
// 当 File 对象超出作用域时,自动释放资源
Ok(file) => file,
Err(err) => panic!("open file failed: {}", err),
};
// file.read_buf()
for ele in io::BufReader::new(file).lines() {
if let Ok(line) = ele {
println!("{line}");
}
}
Ok(())
}
子进程
use std::{io, process::Command};
fn main() -> io::Result<()> {
let output = Command::new("rustc").args(["--version"]).output()?;
if output.status.success() {
let s = String::from_utf8_lossy(&output.stdout);
println!("successded:\n{}", s);
} else {
let s = String::from_utf8_lossy(&output.stderr);
println!("fail:\n{}", s);
}
Ok(())
}