【Rust】基础语法与特点

一、Rust特点

Rust语言与其他语言的明显不同之处是它独特的内存管理方式。Rust 做到了内存安全而无需类似Java 编程语言中实现自动垃圾收集器的开销,这是通过所有权/借用机制、生命周期、以及类型系统来达到的。对Rust的其他特点,以后可以慢慢去体会,个人认为比较独特的是基于trait的泛型,下面是截取Rust官网中对Rust语言特点的说明:

  • 零开销抽象
  • 转移语义
  • 保证内存安全
  • 线程无数据竞争
  • 基于 trait 的泛型
  • 模式匹配
  • 类型推断
  • 极小运行时
  • 高效 C 绑定
二、基础语法

这里主要学习Rust的基础语法概念:变量绑定、原生类型、数组、动态数组、字符串、结构体、枚举、控制流、函数与方法、特性、注释等。如何有别的语言基础,很快就可以掌握。

1、变量绑定

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 表达式实际上是一种模式匹配。

2、原生类型

Rust内置的原生类型 (primitive types) 有以下几类:

  • 布尔类型:有两个值true和false。
  • 字符类型:表示单个Unicode字符,存储为4个字节。
  • 数值类型:分为有符号整数 (i8, i16, i32, i64, isize)、 无符号整数 (u8, u16, u32, u64, usize) 以及浮点数 (f32, f64)。
  • 字符串类型:最底层的是不定长类型str,更常用的是字符串切片&str和堆分配字符串String, 其中字符串切片是静态分配的,有固定的大小,并且不可变,而堆分配字符串是可变的。
  • 数组:具有固定大小,并且元素都是同种类型,可表示为[T; N]。
  • 切片:引用一个数组的部分数据并且不需要拷贝,可表示为&[T]。
  • 元组:具有固定大小的有序列表,每个元素都有自己的类型,通过解构或者索引来获得每个元素的值。
  • 指针:最底层的是裸指针const T和mut T,但解引用它们是不安全的,必须放到unsafe块里。
  • 函数:具有函数类型的变量实质上是一个函数指针。
  • 元类型:即(),其唯一的值也是()。
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);
}

有几点是需要特别注意的:

  • 数值类型可以使用_分隔符来增加可读性。(let a=100_000;)
  • Rust还支持单字节字符b’H’以及单字节字符串b"Hello",仅限制于ASCII字符。 此外,还可以使用r#"…"#标记来表示原始字符串,不需要对特殊字符进行转义。
  • 使用&符号将String类型转换成&str类型很廉价, 但是使用to_string()方法将&str转换到String类型涉及到分配内存, 除非很有必要否则不要这么做。
  • 数组的长度是不可变的,动态的数组称为Vec (vector),可以使用宏vec!创建。
  • 元组可以使用==和!=运算符来判断是否相同。
  • 不多于32个元素的数组和不多于12个元素的元组在值传递时是自动复制的。
  • Rust不提供原生类型之间的隐式转换,只能使用as关键字显式转换。
  • 可以使用type关键字定义某个类型的别名,并且应该采用驼峰命名法。
3、数组、动态数组、字符串

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);
4、结构体与枚举

结构体(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	};
5、控制流

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),必须覆盖所有的可能值。 如果需要得到|或者...匹配到的值,可以使用@绑定变量。

6、函数与方法

函数——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)

一般在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示例

建立一个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.tomlsrc/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!

你可能感兴趣的:(Rust)