Rust语言与其他语言的明显不同之处是它独特的内存管理方式。Rust 做到了内存安全而无需类似Java 编程语言中实现自动垃圾收集器的开销,这是通过所有权/借用机制、生命周期、以及类型系统来达到的。对Rust的其他特点,以后可以慢慢去体会,个人认为比较独特的是基于trait的泛型,下面是截取Rust官网中对Rust语言特点的说明:
这里主要学习Rust的基础语法概念:变量绑定、原生类型、数组、动态数组、字符串、结构体、枚举、控制流、函数与方法、特性、注释等。如何有别的语言基础,很快就可以掌握。
Rust 通过 let 关键字进行变量绑定。
fn main() {
let a1 = 5; //不可变绑定
let a2:i32 = 5;
assert_eq!(a1, a2);
//let 绑定 整数变量默认类型推断是 i32
let b1:u32 = 5;
//assert_eq!(a1, b1);
//去掉上面的注释会报错,因为类型不匹配
//errer: mismatched types
{
let mut a: f64 = 1.0;//rust 在声明变量时,在变量前面加入 mut 关键字,变量就会成为可变绑定的变量。
let b = 2.0f32;//这里的 b 变量,绑定了 2.0f32。这是 Rust 里面值类型显式标记的语法,规定为value+type的形式。
//改变 a 的绑定
a = 2.0;
println!("{:?}", a);
//重新绑定为不可变
let a = a;
//不能赋值
//a = 3.0;
//类型不匹配
//assert_eq!(a, b);
}
}
问题:为什么在 Rust 里面声明一个变量的时候要采用 let 绑定表达式?
解答:那是因为 let 绑定表达式的表达能力更强,而且 let 表达式实际上是一种模式匹配。
Rust内置的原生类型 (primitive types) 有以下几类:
fn main() {
// boolean type
let t = true;
let f: bool = false;
// char type
let c = 'c';//Rust还支持单字节字符b'H'以及单字节字符串b"Hello",仅限制于ASCII字符。 此外,还可以使用r#"..."#标记来表示原始字符串,不需要对特殊字符进行转义。
// numeric types
let x = 42;
let y: u32 = 123_456;//数值类型可以使用_分隔符来增加可读性。
let z: f64 = 1.23e+2;
let zero = z.abs_sub(123.4);
let bin = 0b1111_0000;
let oct = 0o7320_1546;
let hex = 0xf23a_b049;
println!("{}", y);
// string types
let str = "Hello, world!";
let mut string = str.to_string();//使用&符号将String类型转换成&str类型很廉价, 但是使用to_string()方法将&str转换到String类型涉及到分配内存, 除非很有必要否则不要这么做。
// arrays and slices
let a = [0, 1, 2, 3, 4];//数组的长度是不可变的,动态的数组称为Vec (vector),可以使用宏vec!创建。
let middle = &a[1..4];
let mut ten_zeros: [i64; 10] = [0; 10];//不多于32个元素的数组和不多于12个元素的元组在值传递时是自动复制的。
// tuples
let tuple: (i32, &str) = (50, "hello");//元组可以使用==和!=运算符来判断是否相同。
let (fifty, _) = tuple;
let hello = tuple.1;
// raw pointers
let x = 5;
let raw = &x as *const i32;//Rust不提供原生类型之间的隐式转换,只能使用as关键字显式转换。
let points_at = unsafe { *raw };
// functions
fn foo(x: i32) -> i32 { x }
let bar: fn(i32) -> i32 = foo;
// explicit conversion
let decimal = 65.4321_f32;
let integer = decimal as u8;
let character = integer as char;
// type aliases
type NanoSecond = u64;//可以使用type关键字定义某个类型的别名,并且应该采用驼峰命名法。
type Point = (u8, u8);
}
有几点是需要特别注意的:
Rust 使用数组存储相同类型的数据集。 [T; N]表示一个拥有 T 类型,N 个元素的数组。数组的大小是固定。
动态数组是一种基于堆内存申请的连续动态数据类型,拥有 O(1) 时间复杂度的索引、压入(push)、弹出(pop)。如果你熟悉STL中的std::vector
等,查看Rust的源码实现,基本上是差不多的。
let mut vec = Vec::new();//创建空Vec
vec.push(1);
vec.push(2);
assert_eq!(vec.len(), 2);
assert_eq!(vec[0], 1);
assert_eq!(vec.pop(), Some(2));
assert_eq!(vec.len(), 1);
vec[0] = 7;
assert_eq!(vec[0], 7);
vec.extend([1, 2, 3].iter().cloned());
for x in &vec {
println!("{}", x);
}
assert_eq!(vec, [7, 1, 2, 3]);
Rust 里面有两种字符串类型。String 和 str。str 类型基本上不怎么使用,通常使用 &str 类型,它其实是 [u8] 类型的切片形式 &[u8]。这是一种固定大小的字符串类型。 常见的的字符串字面值就是 &'static str 类型。这是一种带有 'static 生命周期的 &str 类型。
let hello = "Hello, world!";// 字符串字面值
let hello: &'static str = "Hello, world!";// 附带显式类型标识
let mut s = String::new();// 创建一个空的字符串,Creates a new empty String.
String 是一个带有的 vec:Vec
成员的结构体,你可以理解为 str 类型的动态形式。 它们的关系相当于 [T] 和 Vec
的关系。 显然 String 类型也有压入和弹出。
let mut s = String::new();// 创建一个空的字符串
let mut hello = String::from("Hello, ");// 从 `&str` 类型转化成 `String` 类型
// 压入字符和压入字符串切片
hello.push('w');
hello.push_str("orld!");
// 弹出字符。
let mut s = String::from("foo");
assert_eq!(s.pop(), Some('o'));
assert_eq!(s.pop(), Some('o'));
assert_eq!(s.pop(), Some('f'));
assert_eq!(s.pop(), None);
结构体(struct)是一种记录类型,所包含的每个域(field)都有一个名称。每个结构
体也都有一个名称,通常以大写字母开头,使用驼峰命名法。rust中没有class类,类这一概念是通过struct结构体实现的,rust中的结构体类似C++中的结构体,也可以把它理解成类。
// structs
struct Point {
x: i32,
y: i32,
}
let point = Point { x: 0, y: 0 };
Rust有一个集合类型,称为枚举 (enum),代表一系列子数据类型的集合。
// enums
enum Message {
Quit,
ChangeColor(i32, i32, i32),
Move { x: i32, y: i32 },
Write(String),
}
let x: Message = Message::Move { x: 3, y: 4 };
if
if
与别的语言不同之处是:Rust中的if
是一个表达式 (expression),可以赋给一个变量:
let x = 5;
let y = if x == 5 { 10 } else { 15 }; //the result is :y=10
for
for与其他语言基本相同。
for i in 0..10{
println!("{}", i);
}
while
while与其他语言无异。
loop
对于无限循环,Rust中有一个专用的关键字loop
。
let mut n=10;
loop{
if n<0{
break;
}else{
println!("{}",n );
n=n-1;
}
}
这里补充一个双重循环break
的问题,在单循环中使用break
跳出循环,但如果遇到双重循环或者更多重循环时怎么办呢?与其他语言类似,使用标记标识跳出指定循环。如下所示:
fn main() {
let a = vec![1;5];
let b = vec![2;6];
'outer: for i in a {
println!("{}", i);
'inner: for j in b.iter() {
print!("{}", j);
break 'outer; // 跳出外层循环,如果不加标记,默认跳出最内层循环
}
}
}
更多可参考:Nesting and labels
match
match有点类似与switch-case。
let day=5;
match day{
1|6=>println!("weekend"),
e@1...5=>println!("weekday{}",e),//包含1和5,闭区间
_=>println!("invalid"),
}
其中|
用于匹配多个值,...
匹配一个范围(包含最后一个值),并且_
在这里是必须的,因为match
强制进行穷尽性检查(exhaustiveness checking),必须覆盖所有的可能值。 如果需要得到|
或者...
匹配到的值,可以使用@
绑定变量。
函数——rust中函数用关键字fn
,形式与别的语言稍有不同:
fn add_one(x: i32) -> i32 {
x + 1
}
匿名函数——Rust使用闭包来创建匿名函数,可以用lambda表达式来理解:
let mut num = 5;
{
let mut add_num = move |x: i32| num += x; //闭包通过move获取了num的所有权
add_num(5);
}
assert_eq!(5, num);//下面的num在被move之后还能继续使用是因为其实现了Copy特性
方法
Rust通过 impl 关键字在 struct 、 enum 或者 trait 对象上实现方法调用语法 (method call syntax)。 关联函数 (associated function) 的第一个参数通常为 self 参数,不含 self 参数的关联函数称为静态方法(static method)。
struct Circle {
x:f64,
y:f64,
radius:f64,
}
impl Circle {
//静态方法
fn new(x:f64,y:f64,radius:f64)->Circle{
Circle{
x:x,
y:y,
radius:radius,
}
}
fn area(&self)->f64{
std::f64::consts::PI*(self.radius*self.radius)
}
}
fn main() {
let c=Circle{x:0.0,y:0.0,radius:2.0};
println!("{}",c.area() );
println!("{}",Circle::new(0.0,0.0,2.0).area() );
}
特性
为了描述类型可以实现的抽象接口 (abstract interface), Rust引入了特性 (trait) 来定义函数类型签名 (function type signature),类似与C#语言中的接口的概念:
trait HasArea {
fn area(&self) -> f64;
}
struct Circle {
x: f64,
y: f64,
radius: f64,
}
impl HasArea for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * (self.radius * self.radius)
}
}
struct Square {
x: f64,
y: f64,
side: f64,
}
impl HasArea for Square {
fn area(&self) -> f64 {
self.side * self.side
}
}
需要注意的一点是Rust并不是严格意义的面向对象编程语言,也不是面向过程编程语言,但在一定程度上又有面向对象语言的特点,有一定的抽象。
一般在Linux环境下开发,安装rust,然后根据自己的编辑器配置rust开发工具。关于安装,参考官网:https://www.rust-lang.org/en-US/install.html。
安装后你还需要学习一下Rust 版本管理工具rustup,它最直接的功能是:安装和更新来自 Rust 的发布通道: nightly, beta 和 stable。
https://github.com/rust-lang-nursery/rustup.rs/blob/master/README.md
https://rustcc.gitbooks.io/rustprimer/content/install/rustup.html
可以选择IntelliJ+IntelliJ Rust插件进行开发。看个人使用习惯,选择自己熟悉的编辑器即可。
建立一个helloworld工程。开始之前需学习一下cargo项目管理器,作为rust的代码组织管理工具,cargo提供了一系列的工具,从项目的建立、构建到测试、运行直至部署,为rust项目的管理提供尽可能完整的手段。
运行cargo new helloworld --bin
建立一个工程
sl@Li:~/Works/study$ cargo new helloworld --bin
Created binary (application) `helloworld` package
sl@Li:~/Works/study$ cd helloworld/
sl@Li:~/Works/study/helloworld$ tree .
.
├── Cargo.toml
└── src
└── main.rs
1 directory, 2 files
可以看到自动生成了Cargo.toml
和src/mian.rs
.
编译cargo build
sl@Li:~/Works/study/helloworld$ cargo build
Compiling helloworld v0.1.0 (/home/sl/Works/study/helloworld)
Finished dev [unoptimized + debuginfo] target(s) in 3.42s
运行cargo run
sl@Li:~/Works/study/helloworld$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/helloworld`
Hello, world!