本文内容摘自《Rust 程序设计语言》
Hello World
fn main() {
println!("Hello, world!");
}
使用rustc main.rs
编译。
!
意味着使用一个宏,而非一个函数。
Cargo
用途一:用于构建项目。
cargo new hello
构建一个名为hello的项目。
cargo check
编译检查,cargo build
编译当前项目,cargo run
编译并运行当前项目。
cargo clean
清理目标文件。
用途二:用于管理依赖。
rand是一个rust crate,可以理解为库、代码包,都是类似的概念。
首先需要在Cargo.toml
中引入依赖。
[dependencies]
rand = "0.9.0"
[source.crates-io]
replace-with = 'tuna'
[source.tuna]
registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git"
cargo build
时会自动从crates.io上获取依赖,国内网络最好还是换个源。
Applets
通过一个猜数字的小程序来快速上手Rust语言。
use std::io;
use rand::Rng;
fn main() {
println!("Guess the number!");
let randnum = rand::thread_rng().gen_range(1..101);
println!("Please input your guess.");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("Failed to read line.");
let guess: u32 = guess.trim().parse().expect("Please type a number.");
println!("You guessed: {}, actually: {}.", guess, randnum);
}
let mut i = 0;
可以理解为int i = 0;
,let i = 0;
可以理解为int const i = 0;
。
fun(&mut i);
类似fun(int const& i);
。
read_line()
和parse()
返回一个enum Result {Ok, Err}
,expect()
在返回Err后会终止程序并报错,这可比if err != nil
优雅多了。
let guess: u32
显示定义了一个类型为u32的新变量,Rust允许使用相同的变量名,尤其是在类型转换这种场景下。
trim()
终于去除字符串前后的空白字符,parse()
用于解析字符串为数值。
use std::io;
use std::cmp::Ordering;
use rand::Rng;
fn main() {
println!("Guess the number!");
let randnum = rand::thread_rng().gen_range(1..101);
loop {
println!("Please input your guess.");
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("Failed to read line.");
let guess:u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
match guess.cmp(&randnum) {
Ordering::Less => println!("Too small."),
Ordering::Greater => println!("Too big."),
Ordering::Equal => {
println!("You win.");
break;
}
}
}
}
loop这里是个无限循环,通过break跳出。
match会执行匹配的那一项,需要cover所有可能。这里的cmp()
和parse()
都返回一个枚举值。
Const
let i = 0;
和let mut i = 0;
容易让人理解为常量和变量,但实际上这两者都是变量,Rust中通过const i: u32 = 0;
定义一个常量。
let i = 0;
let i = 1;
let mut i = 2;
let i = "3";
区别在于const必须指定类型,且不能使用相同的名字,类似上面这样。
Type
整型
i8
、i16
、i32
、i64
、i128
、isize
、u8
、u16
、u32
、u64
、u128
、isize
数值不论机器默认类型i32
,其中isize
和usize
在32位机上是32位,64位机上是64位。
浮点型
f32
、f64
布尔型
bool
字符型
char
Rust中的字符型是4个字节,采用unicode编码,可以表示更多的字符,u8字面值则需要b'A'
这样表示。
元组
let tup: (i32, f32, u32) = (18, 1.0, 36);
let x = tup.0;
let y = tup.1;
let z = tup.2;
let (x, y, z) = tup;
数组
let arr: [i32; 5] = [0, 0, 0, 0, 0];
let arr: [0; 5];
let first = arr[0];
数组所有元素是相同类型,这是和元组的区别。
Function
语句是有分号结尾的,而表达式没有。
let i = {
let i = 0;
i + 1
};
i + 1
让代码块有了返回值,但如果i + 1;
则编译不通过。
无返回值的函数
fn foo(i: i32, j: i64) {
println!("{} {}", i, j);
}
fn main() {
foo(32, 64);
}
有返回值的函数
fn foo(i: i32, j: i32) -> i32 {
println!("{} + {}", i, j);
i + j
}
fn lot() -> (i32, f32) {
(1, 1.0)
}
fn main() {
println!("{} {} {}", foo(1, 2), lot().0, lot().1);
}
Control
if else
let i = if true {
1
} else if false {
2
} else {
3
};
loop
上面提过了,死循环,这里可以通过break让loop返回一个值。
let result = loop {
break counter * 2;
};
while
和go里的while一样。
while true {}
for
let a = [10, 20, 30, 40, 50];
for element in a.iter().rev() {
println!("the value is: {}", element);
}
for i in (0..5).rev() {
println!("the value is: {}", a[i]);
}
这里rev()
反向遍历,去掉就是正向了。
Ownership
不同于使用GC或手动管理内存,Rust通过所有权系统管理内存。
Rust中每一个值,在任意时刻,有且仅有一个Owner,因此Rust中没有浅拷贝的概念。
{
let s = String::from("hello"); // s 生效
} // s 失效
离开作用域时会自动调用drop()
清理堆内存。
let s1 = String::from("hello");
let s2 = s1;
上面的代码既不是浅拷贝,也不是深拷贝,而是移动,所有权转交给s2,s1失效。
let s1 = String::from("hello");
let s2 = s1.clone();
深拷贝语义需要显示指定,clone()
应该谨慎调用,耗费资源。
上面所说不论是move还是clone都是指堆数据,栈数据都是copy语义。
fn main() {
let s = String::from("hello");
takes_ownership(s); // s 所有权转移
// println!("{}", s); // s 失效
} // s 失效因此不会drop
fn takes_ownership(s: String) {
println!("{}", s);
} // drop释放堆内存
Reference
fn main() {
let s = String::from("hello");
takes_ownership(&s);
println!("{}", s); // s 依旧有效
} // s drop
fn takes_ownership(s: &String) {
println!("{}", s);
}
引用允许使用值但不发生所有权的转移,获取引用的操作称为借用。
上面代码中借用来的s是不可变的,如需修改使用可变引用即可。
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(s: &mut String) {
s.push_str(", world");
}
需要注意的是,Rust中一个作用域内不允许相同数据的两次可变借用,也不允许一个作用域内相同数据的借用和可变借用共存,有点类似读写锁的意思了,下面两段代码都不行。
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s; // 不行
println!("{}, {}", r1, r2);
let mut s = String::from("hello");
let r1 = &s; // 可以
let r2 = &s; // 可以
let r3 = &mut s; // 不行
println!("{}, {}, {}", r1, r2, r3);
需要注意引用的作用域从声明开始,到最后一次使用终止,而不是代码块,如果上面代码中没有println!使用引用,那么引用的作用于就只在声明那一行。
Rust在编译层面不允许悬垂引用的发生。
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s // 不行
}
Slice
Slice允许引用集合中的连续一段元素,也是引用,是不需要所有权的。
let s = String::from("hello world");
let hello = &s[..5];
let world = &s[6..];
println!("{}, {}", hello, world);
字符串Slice的类型为&str,下面代码用于获取第一个单词。
fn main() {
let s = String::from("hello world");
let hello = first_word(&s);
// s.clear(); // 不行
println!("{}", hello);
}
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
Rust保证Slice有效,因此s.clear()
是不被允许的,原理是需要获取一个可变引用。
字符串字面值也是一个Slice,字符串的操作可以更简便了。
fn main() {
let s = "hello world";
let hello = first_word(s);
let hello = first_word("hello world");
println!("{}", hello);
}
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
Struct
fn main() {
let mut user = User {
username: String::from("admin"),
password: String::from("12345"),
};
user.password = String::from("54321");
}
struct User {
username: String,
password: String,
}
let mut user
设置user整体可变,不能单独设置某个字段可变。
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
元组结构体可以给元组一个名字,black和origin不是一个类型。
结构体支持定义方法,create在这里就类似静态方法,Rust称为关联函数。
fn main() {
let mut user = User::create("admin", "12345");
user.modify_password("54321");
println!("{:#?}", user);
}
#[derive(Debug)]
struct User {
username: String,
password: String,
}
impl User {
fn modify_password(&mut self, new_password: &str) {
self.password = String::from(new_password);
}
fn create(username: &str, password: &str) -> User {
User {
username: String::from(username),
password: String::from(password),
}
}
}
想直接输出一个结构体需要重载Display方法,但也可以把结构体定义为Debug模式,通过{:?}
或{:#?}
来输出。
Enum
应用场景是某一样事物其类型是有限个的,比如IP地址只有IPv4和IPv6两种类型。
枚举的精髓在于其成员都是相同类型,这里有点多态的意思了。
enum Message {
Quit,
Move{x: i32, y: i32},
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn call(&self) {}
}
fn foo(m: &Message) {}
fn main() {
let m = Message::Write(String::from("hello"));
foo(&m);
m.call();
}
Rust中没有null的概念,如果需要一个对象为空,要使用Option枚举类型。
enum Option<T> {
Some(T),
None,
}
上文提到过match,用于匹配枚举成员,我们可以使用match从枚举中取值。
impl Message {
fn call(&self) {
match self {
Message::Quit => println!("quit"),
Message::Move{x, y} => println!("{} {}", x, y),
Message::Write(s) => println!("{}", s),
Message::Color(r, g, b) => println!("{} {} {}", r, g, b),
}
}
}
match需要cover所有可能,那对于非枚举类型怎么办呢?可以使用_
通配。
match value {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
_ => (),
}
如果只匹配一种可能可以使用if let,下面两种方式等价。
match opt {
Some(3) => println!("three"),
_ => (),
}
if let Some(3) = opt {
println!("three");
}
if let工作方式和match一致,也可以获取枚举成员值,也可以和else联合使用。
if let Message::Write(s) = m {
println!("{}", s);
} else {
println!("not write");
}
Package
一个package有n个crate(bin),至多1个crate(lib),至多1个cargo.toml。cargo.toml用于描述如何构建crate,package中最少有一个crate,无论是crate(bin)或crate(lib)。
src/main.rs是crate(bin) root,这个文件是必须有的;src/lib.rs是crate(lib) root,这个文件可以没有。这两个crate root名字都和package相同。src/bin/*.rs用于构建crate(bin)。
一个crate可以有n个module,按照一棵树去组织。
mod mod1 {
pub mod mod1_1 {
pub fn test() {}
}
}
mod mod2 {
pub fn test() {
super::mod1::mod1_1::test();
}
}
pub fn test() {
crate::mod2::test();
mod2::test();
}
和C++类似,也可以使用use、use as关键字来导入,pub use可以让使用者不需要导入就可以使用。
vector
fn main() {
let v: Vec<i32> = Vec::new();
let mut v = vec![1, 2, 3];
v.push(4);
let third: &i32 = &v[2];
println!("{}", third);
match v.get(0) {
Some(&i) => println!("{}", i),
None => println!("none"),
}
for i in &mut v {
*i += 1;
}
for i in &v {
println!("{}", i)
}
}