rust的下载
rust的下载地址是: https://www.rust-lang.org/learn/get-started
其中, 如果用的是Unix系统, 用这一行命令就可以完成下载:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
之后, 如果你想更新rust
, 可以用rustup update
这个命令.
Cargo
Cargo相当于是rust工程的管理工具, 类似于Python的pip.
Cargo的安装目录在~/.cargo
中, 用cargo
下载的包全都在这里.
rust的Hello World
rust的Hello World如下:
fn main() {
println!("Hello World");
}
使用rustc
编译:
rustc test.rs
然后运行:
./test
其中, println!()
是换行打印, print!()
是不换行打印.
rust注释
rust的注释和C/C++一样, 单行是//
, 多行是/**/
rust还有一种特殊的多行注释///
, 在这种多行注释中可以使用markdown进行渲染:
/// Adds one to the number given.
///
/// # Examples
///
///
/// let x = add(1, 2);
///
///
fn add(a: i32, b: i32) -> i32 {
return a + b;
}
fn main() {
println!("{}",add(2,3));
}
在这种注释下, 开发者可以使用cargo doc
功能自动生成具有可读性的文档.
rust格式化打印
rust中使用{}
来进行格式化打印, 例如:
println!("a is {}, b is {}, c is {}", a, b, c);
rust转义字符
rust中转义字符和C/C++一样, 都是\
.
但是对于两个字符特殊, 这两个字符是{
和}
.
如果要打印{
:
println!("{{");
```
如果要打印`}`:
```rust
println!("}}");
rust变量的定义
首先, rust是一种强类型语言.
如果要定义变量, 可以使用let
关键字, 例如:
let a = 123;
其中, rust会自动判别变量的类型.
首先, 由于rust是强类型语言, 这种写法会被报错:
a = "abc"
其次, rust不允许==有精度损失的类型转换==, 因此, 下面的写法会被报错:
a = 4.56;
再次, 如果只是用let
定义变量, 那么变量默认为不可变, 因此, 下面的写法会被报错:
a = 456;
但是, 不可变的变量可以被重新绑定, 因此, 下面的写法是正确的:
let a = 123;
let b = 456;
这种重新绑定在rust中叫做重影(Shadowing).
如果要定义一个可变的变量, 那么可以用mut
关键字:
let mut a = 1;
a = 2;
但是, 由于rust是强类型的语言, 可变的变量只有值可以改变, 类型不能变化, 因此, 下面的代码会报错:
let mut s = "123";
s = s.len()
rust数据类型
如果要在变量定义的时候要定义上数据类型, 可以这样写:
let mut x : f32 = 3.0;
let y : f64 = 1.0;
rust整数类型
几位 | 无符号 | 有符号 |
---|---|---|
8位 | u8 | i8 |
16位 | u16 | i16 |
32位 | u32 | i32 |
64位 | u64 | i64 |
128位 | u128 | i128 |
rust浮点数
浮点数有f32
和f64
两种.
rust布尔类型
布尔类型的关键字是bool
, 值只能是true/false
.
rust字符类型
字符类型的关键字是char
, 注意, ==在rust中, 字符一共占用4个字节==.
rust数组类型
rust数组下标也是从0开始.
rust使用[]
来定义一个数组, 数组中的元素必须是同一类型:
let a = [ "January", "February", "March" ];
如果要声明数组类型:
// 类型是i32, 长度是5的数组
let b: [i32; 5] = [1, 2, 3, 4, 5]
如果要定义一个数组元素相同的元素, 可以:
// 元素是3, 长度是5的数组
let c = [3; 5]
rust的tuple类型
rust中的tuple可以包含不同的元素, 使用()
定义一个tuple:
let tup: (i32, f64, u8) = (500, 6.4, 1)
可以把元组中的变量拆开赋给不同的变量:
let (x, y, z) = tup;
rust运算符
加法: +
减法: -
乘法: *
除法: /
- 和C/C++的用法一行, 整数除法是向下取整
取模: %
rust中没有++
和--
, 只有+=
和-=
.
rust函数
rust的函数定义如下:
fn main() {
println!("Hello World");
}
如果要带参数和返回值:
fn add(a: i32, b: i32) -> i32 {
return a + b;
}
函数体表达式
rust中, 可以使用{}
定义一个比较复杂的表达式, 定义的文法如下:
{
// Statement也可以没有
Statement
Expression
}
其中, Statement
是末尾带;
的那种语句, 而Expression就是单独的表达式, 例如:
let y = {
let x = 3;
x + 1
};
此时, y
的值就是4.
rust条件语句
rust条件语句的格式如下:
fn main() {
let a = 12;
let b;
if a > 0 {
b = 1;
}
else if a < 0 {
b = 2;
}
else {
b = 0;
}
}
需要说明几点:
- 按照rust的要求,
if
或者else if
后面必须是bool
类型. if/else if
后面不需要加()
, 但是不是不可以.- 无论后面是不是单个语句, 都必须用
{}
.
结合函数体表达式, rust中的三目运算符可以定义为:
let number = if a > 0 {1} else {-1};
rust循环语句
while循环的格式:
while number != 4 {
//...
}
For循环遍历数组中的值, 可以使用iter()
迭代器:
let a = [1, 2, 3, 4, 5];
for item in a.iter() {
// item去除的是数组的值
println!("{}", item)
}
如果要数组下标, 可以用a..b
, 表示$[a, b)$:
for i in 0..5 {
println!("a[{}] = {}", i, a[i]);
}
其中:
..y
: 表示$[0, y)$x..
: 表示$[x, end)$..
: 表示从0到结束.
rust中还支持一种无限循环loop
, 相当于while (true)
loop {
//...
}
退出循环可以使用break
.
其中, rust有一种新东西, 可以在loop
循环中使用break
关键字, 实现return
类似的功能, 可以给循环一个返回值, 这样就不用使用额外的变量记录循环中的值了, 例如:
let a = [1, 2, 3, 4, 5];
let mut i = 0;
// 查找数组中元素2的下标
let location = loop {
let ch = a[i];
if a[i] == 2 {
break i;
}
i += 1;
}
注意, 这种写法只针对loop
循环, 其他循环不能使用.
rust的所有权
在rust中, 每一个内存中的每一个值就相当于一个房子, 它都有一个所有者(owner), 这个所有者就是指向这个值的变量.
在进行各种各样的操作时, 所有权会发生各种各样的变化.
赋值操作中所有权的变化
基本类型
rust中的基本类型有如下几类:
- 所有整数类型.
- 所有浮点数类型.
- 布尔类型bool.
- 字符类型char.
- 仅包含以上类型的tuple.
基本类型存储在栈上, 如果执行赋值操作会发生如下变化:
- 栈上的值会被复制一份.
- 被赋值的变量会指向刚出炉的被复制的值.
例如:
let x = 5;
let y = x;
当执行完let y = x;
后, y
和x
是毫不相干的变量, 有点类似于深拷贝.
非基本类型
非基本类型的值基本存储在堆(heap)中.
如果执行赋值操作, 会发生所有权的交换, 例如:
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1); // 错误!s1 已经失效
在执行完let s2 = s1;
之后, String::from("hello")
这个数据的所有权已经从s1
转移到了s2
, s1
已经不能再使用了.
函数参数传递所有权的变化
基本类型
如果基本类型变量被当作函数参数, 那么函数参数是原来的变量在栈上复制得到的, 不会发生所有权的变化.
fn main() {
let x = 5;
function(x);
// 在这里x还是有效的.
}
非基本类型
如果非基本类型被当作函数参数, 那么值的所有权会从原变量转移到函数参数, 原变量的引用就会失效, 例如:
fn main() {
let s1 = String::from("hello");
function(s1);
// 此处, s1会失效, 不能再次使用.
}
因此, 为了不让变量因为函数调用而失效, 一般非基本类型作为函数参数, 都传递引用.
函数返回值所有权的变化
基本类型
非基本类型
克隆
对于非基本类型的变量可以使用clone()
, 这个方法干了如下几件事情:
- 将堆中的数据复制一份.
- 创建一个新的引用, 指向这个数据.
也就相当于深拷贝.
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone();
// s1与s2是独立的
println!("s1 = {}, s2 = {}", s1, s2);
}
引用
不可变引用
引用可以让两个变量共享一个值的所有权, 例如:
fn main() {
let s1 = String::from("hello");
let s2 = &s1;
// s1, s2同时指向堆中的同一个地址
}
在函数参数传递时, 也可以传递引用:
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
注意, 考虑这种情况:
let s1 = String::from("hello");
let s2 = &s1;
let s3 = s1;
s2
与s1
共享一个值之后, 所有权又从s1
转移到了s3
, 此时s1
失效, s2
也会失效, 需要重新建立引用:
let s1 = String::from("hello");
let mut s2 = &s1;
let s3 = s1;
s2 = &s3;
可变引用
注意, 建立引用后, 不能通过引用修改数据, 例如:
let s1 = String::from("hello");
let s2 = &s1;
// 无法修改, 只能使用
s2.push_str("haha");
如果要修改, 可以使用mut
修饰的引用类型 (可变引用), 例如:
let s1 = String::from("hello");
let s2 = &mut s1;
s2.push_str("haha");
多重引用
注意, 不可变引用可以多重引用, 可变引用不能进行多重引用, 例如:
let s = String::from("hello")
// 不可变引用, 可以多重引用
let r1 = &s;
let r2 = &s;
// 可变引用, 不能多重引用
let R1 = &mut s;
let R2 = &mut s; // 报错
垂悬引用 (Dangling Reference)
本质上就是一个引用, 指向了被释放的变量值, 相当于空指针.
例如如下例子:
fn main() {
let a = function();
}
fn function() -> &String {
let s = String::from("hello");
// 函数结束后, s会被释放, 返回的引用就是空指针, Dangling Reference.
return &s;
}
rust中切片类型(Slice)
字符串切片
字符串切片的定义如下:
let s = String::from("hello")
let a = &s[0..5]
如果一个字符串被切片, 那么原字符串不能修改:
fn main() {
let mut s = String::from("hello");
let slice = &s[0..3];
s.push_str("haha"); // 会报错
}
数组切片
数组切片的定义如下:
fn main() {
let arr = [1, 3, 4, 5, 5];
let part = &arr[0..3];
}
rust结构体
结构体定义与访问
rust结构体定义如下:
struct Person {
name: String,
age: u32,
home: String
}
注意结构体定义最后没有;
.
创建结构体变量:
let p = Person {
name: String::from("haha"),
age: 18,
home: String::from("fandouhuayuan")
}
访问结构体变量可以使用.
:
println!("{}, {}, {}", p.name, p.age, p.home);
假设你想创建一个结构体变量, 这个变量大部分属性和现有的结构体变量一致, 只有少部分属性不一样, 可以用这种写法:
let p = Person {
name: String::from("haha"),
age: 18,
home: String::from("fandouhuayuan")
}
let another_p = Person {
name: String::from("niuniu"),
// ..关键字
..p
}
tuple结构体
tuple结构体主要用来处理那些经常定义, 但是又不太复杂的数据:
定义方式如下:
struct Point(f64, f64);
创建实例:
let origin = Point(0.0, 0.0);
访问成员变量:
println!("{}, {}", origin.0, origin.1);
打印结构体
如果要打印结构体, 首先要引入调试库, 在最开头加上这一句:
#[derive(Debug)]
然后, 用{:?}
这个占位符打印结构体:
let rect1 = Rectangle {
width: 30,
height: 50
};
println!("rect1 is {:?}", rect1);
如果结构体的成员变量太多了, 可以用{:#?}
进行打印, 这样每个成员变量就会单独占一行.
结构体方法
成员方法
结构体方法需要用impl
和self
关键字来定义, 例如:
/* 定义成员变量 */
struct Rectangle {
width: u32,
height: u32
}
/* 定义成员方法 */
impl Rectangle {
/* 第一个参数需要是&self */
fn area(&self) -> u32 {
/* 访问成员变量用self.xxx */
return self.width * self.height;
}
fn wider(&self, rect: &Rectangle) -> bool {
return self.width > rect.width;
}
}
注意, impl
一个结构体可以写很多个, 最终效果相当于他们拼接.
结构体关联函数
结构体关联函数有点类似于静态方法, 不依赖于某一个实例, 只要在impl
块中不加&self
就是静态方法.
例如:
struct Rectangle {
width: u32,
height: u32
}
impl Rectangle {
/* 结构体关联函数 */
fn create(width: u32, height: u32) -> Rectangle {
return Rectangle { width, height };
}
}
调用时需要使用::
:
let rect = Rectangle::create(30, 50);
可以使用结构体关联函数定义构造方法/静态方法.
结构体所有权问题
考虑如下代码:
struct Person {
name: String,
age: u32
}
fn main() {
let p = Person {
name: String::from("haha"),
age: 18
};
/* 此时, 结构体p中的name所有权已经交给了str */
let str = p.name;
/* 再次访问p.name会报错 */
println!("{}", p.name);
}
应该改成: let str = p.name.clone()
.
rust枚举
枚举可以这样定义:
enum Book {
Papery, Electronic
}
创建枚举变量:
let book = Book::Papery;
打印枚举变量:
- 首先需要在开头引入调试库:
#[derive(Debug)]
- 然后使用
{:?}
占位符打印:
println!("{:?}", book);
rust工程结构
首先, 可以使用cargo
创建一个标准的rust项目:
cargo new "myproject"
这个myproject
就是package的名字.
然后, 你可以在src
下面创建很多文件夹, 文件夹中写很多的rs
文件.
组织方式如下:
一个rust文件相当于一个
module
, 模块名字就是文件名, 如果要暴露出来, 需要在函数/类前面用pub
.写完之后, 文件夹里需要创建一个
mod.rs
, 把文件夹变成module, 然后在里面引入这个文件夹中的所有模块:pub mod module1; pub mod module2; ///
然后在最顶层的
lib.rs
中导入所有顶层的模块, 例如:. ├── lib.rs ├── main.rs ├── net │ ├── mod.rs │ ├── netutils.rs │ └── test.rs └── utils.rs
lib.rs
就只需要导入pub mod net;
和pub mod utils
即可, 然后net
中的mod.rs
再递归导入.
在
main.rs
中, 如果要引用一个子模块中的函数, 需要用以下路径:myproject::net::test::xxx
, 注意开头是myproject
.
如果要跨文件引用, 例如要在
test.rs
中用utils.rs
的东西, 需要用到以下路径:crate::utils.rs
, 注意开头是crate
.
rust泛型
函数泛型
函数泛型定义如下:
fn max<T>(array: &[T]) -> T {
// ....
}
结构体泛型
结构体泛型的定义格式如下:
struct Point<T> {
x : T,
y : T
}
// 注意impl后面也要有<T>
impl<T> Point<T> {
// 函数名后面也要有<T>
fn getX<T>(&self) -> &T {
return &self.x;
}
}
使用时:
let p = Point { x : 1, y : 2 };
println!("{}", p.getX());
rust的trait
trait的基本用法
rust中也提供了和Java接口类似的东西, 叫做trait, trait中可以包含方法的定义, 也可以实现一个方法.
- 如果包含了方法的定义, 那么这个trait可以绑定到一个具体的类上, 然后根据这个类的具体情况实现其中的方法.
- 如果trait中本身就实现了方法, 那么这些方法就是default trait, 如果将来这个trait绑定到了某个类, 这个类可以选择重写这个default trait, 也可以不重写, 直接作为默认的方法.
例如我定义了一个类:
struct Person {
name: String,
age: u8
}
我规定一个trait, 要求重写打印这个类的方法:
trait Descriptive {
fn describe(&self) -> String;
}
然后, 我可以将这个trait绑定到这个类, 然后这个类负责重写:
impl Descriptive for Person {
fn describe(&self) -> String {
/* ... */
}
}
trait作为函数参数
假设我定义了一个trait叫做Descriptive
, 那么, 有一种类型叫做impl Descriptive
, 这个类型的含义叫做:
实现了Descriptive
这个trait的对象.
如果作为函数参数:
fn output(object: impl Descriptive) {
println!("{}", object.describe());
}
那么只有实现了Descriptive
trait的对象才能被放到这个函数中.
还有一种写法是结合泛型:
fn output<T: Descriptive>(object: T) {
println("{}", object.describe());
}
如果涉及到了多个trait, 可以用+
连接:
fn output<T: Descriptive + ABABA>(object: T) {
println("{}", object.describe());
}
例如一个取数组中最大值的例子, 我首先需要实现一个比较大小的trait:
trait Comparable {
/* self是变量, 相当于this, &Self是类型, 相当于this的类型 */
fn compare(&self, object: &Self);
}
/* 对浮点数实现 */
impl Comparable for f64 {
fn compare(&self, object: &f64) {
if &self > &object { 1 }
else if &self == &object { 0 }
else { -1 }
}
}
然后定义一个泛型函数, 要求函数参数必须实现Comparable
接口:
fn max<T: Comparable>(array: &[T]) {
// ...
}
trait作为函数返回值
trait如果作为函数返回值, 表示如下几个意思:
要求这个函数返回的对象必须实现指定的trait.
函数内所有可能的返回值类型必须完全相同:
假设有A, B两个类实现了Descriptive trait, 下面的代码还是错误的:
fn function(bool b) -> impl Descriptive { if b { A{} } else { B{} } }
因为,
A
和B
两个类型不同.
用trait约束泛型类的impl
假设有一个泛型类, 他有很多impl
实现的成员方法, 我可以实现: 如果这个泛型类有实现某些trait, 那么它就可以拥有某些成员方法, 例如下面这个例子:
// 泛型类A
struct A<T> {
}
// 如果A实现了B和C这两个trait, 那么A就具有下面的成员方法
impl<T: B + C> A<T> {
// ...
}