当前位置:   article > 正文

【RUST编程之道】RUST快速入门_rust 快速入门

rust 快速入门


由于博主是Java开发,会通过Java与Rust语言进行对比学习,有问题希望大家指出,互相沟通学习。

一. RUST语言的基本构成

  1. 语言规范
  2. 编译器
    官方编译器为rustc,负责将RUST源代码编译为可执行文件或其他库文件。
  3. 核心库
    核心库为标准库的基础,不依赖于操作系统和网络,比如编写一个操作系统或者嵌入式,就无法使用到标准库的信息。
    如一些基础的trait,Copy,Debug,Option,assert!,env!,bool,i32/u32,slice,tuple等。
  4. 标准库
    标准库比较依赖于平台,比如并发,IO,消息传递,以及TCP,UDP等。
  5. 包管理器
    按照一定规则组织的多个rs文件编译后就得到一个包(crate),一般常用的包管理器为Cargo。

二. 变量的绑定

在Rust中,变量通过let关键字来创建。

fn main() {
	let first = 1;
	println!("first number is {}", first); // first number is 1。
	}
  • 1
  • 2
  • 3
  • 4

上述表明了标识符first与值1的绑定关系。

1.位置表达式和值表达式

位置表达式是表示内存位置的表达式,比如:

  1. 本地变量
  2. 静态变量
  3. 解引用
  4. 数组索引
  5. 字段引用
  6. 位置表达式
    通过位置表达式可以对某个数据单元的内存进行读写。

值表达式一般只是引用了某个存储单元地址中的数据,只能进行读取操作

位置表达式一般代表了持久性数据,值表达式代表了临时数据。值表达式要么是字面量,要么是求值过程中创建的临时值。
  • 1

2.不可变绑定与可变绑定

前面所说的let关键字默认声明的位置表达式为不可变的

fn main() {
	let a = 1;
	//a = 2; //无法改变a的值,因为默认为不可变绑定
	let mut b = 2;
	b = 3 //加入mut关键字,声明为可变位置表达式。
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

let默认声明的不可变绑定只能对相应的存储单元进行读取,加入mut关键字后可进行写入操作。

3.所有权和借用

先看代码,在下面代码中

fn main() {
	let str1 = "hello";
	let str2 = "hello".to_string();
	let other = str1;
	println!("{:?}", str1);
	let other = str2;
	println!({"?}", str2);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

每个变量绑定实际上都是拥有该存储单元的所有权,这种转移内存地址的行为就是所有权转移,在RUST中被称为移动,那种不转移的情况被称为复制。
在日常开发中,有时候无需转移所有权,可以通过借用操作符&,直接取内存位置,可以通过该内存位置对存储进行读取。

fn main() {
	let a = [1,2,3];
	let b = &a; //借用a,a的所有权不转移。
	println("{:?}", b); //打印a的地址。
	let mut c = vec![1,2,3];
	let d  = &mut c; //变量c的可变借用
	d.push(4);
	println("{:?}", d); //打印结果为[1,2,3,4];
	let e = &42;
	assert_eq!(42, *e); //*为解引用符号, 可直接获取到e的具体值。
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
上述代码使用&借用符后,将赋值表达式右侧变成了位置上下文,只是共享内存地址。
  • 1

三.函数与闭包

1.函数的定义

函数通过fn关键字定义。如下列代码:

pub fn fizz_buzz(num: i32) -> String {
	if num % 15 == 0 {
		return "fizzbuzz".to_string();
	} else if num % 3 == 0 {
		return "fizz".to_string();
	} else if num % 5 == 0 {
		return "buzz".to_string();
	} else {
		return num.to_string();
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

上述代码中,通过关键字fn定义一个fizz_buzz的函数,函数中的括号为入参类型,约定入参类型为i32,符号->后面的String为该函数的返回类型,该函数定义的返回类型为String。

2.作用域与生命周期

简单来讲,一个大括号就是一个生命周期,Rust语言的作用域是静态作用域。

fn main() {
	let v = "hello world";
	let v = "hello rust";
	{
		let v = "hello handsome boy";
		println!("{?}", v);//新的作用域,这里输出的是hello handsome boy
	}
	println!("{"?}", v); //由于在同一个作用域中,第二个覆盖了第一个,所以这里输出的是hello rust。这种情况被称为变量屏蔽。
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

上述代码中,虽然是同样的代码变量,但是在不同的作用域,所以拥有不同的生命周期。

3.函数指针

万物皆与函数,函数在Rust语言中分量很大,所以函数本身可以作为其他函数的参数和返回值使用。
下面是函数作为参数的情况:

//op为一个定义的函数
pub fn math(op: fn(i32,i32) ->i32, a: i32, b: i32) -> i32 {
	op(a,b)
}
fn sum(a:i32, b:i32) -> i32{
	a+b
}
fn product(a:i32, b:i32) -> i32 {
	a*b
}
fn main() {
	let a = 2;
	let b = 3;
	let sum_res = math(sum, a, b);
	let product_res = math(product, a, b);
	println!("the sum result is {:?}", sum_res);//5
	println!("the product result is {:?}", product_res);//6
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

下面是函数作为返回值的情况:

fn is_true() -> bool {
	true
}
fn true_maker() ->fn() -> bool {
	is_true//这里不能加上(),如果加上()后会直接调用is_true这个函数,不加则是返回同名函数的指针。
}
fn main() {
	(true_maker())();//由于true_maker返回的是is_true的指针,加上()后则是直接调用该函数
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

4.闭包

闭包的特点:

  1. 类似于Java中的匿名函数
  2. 可以捕获到上下文环境中的自由变量
  3. 可以推断出输入和返回的类型

下面是闭包的演示代码:

fn main() {
	let out = 42;

	fn add(i: i32, j: i32) -> i32 {
		i+j
	}
	let closure_annotated = |i: i32, j: i32| -> i32 {
		i+j+out
	}; //由于闭包理解成一个变量形式,所以需要用;结尾
	let closure_inferred = |i, j| i+j+out;
	let i = 1;
	let j = 2;
	println!("{:?}",add(i,j)); //3
	println!("{:?}",closure_annotated(i, j)); //45
	println!("{:?}",closure_inferred(i, j));  //45
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
闭包和函数的重要区别在于,闭包可以捕获到外部变量,但是函数不可以。
比如上述方法中的add改为fn add(i:i32,j:i32)->i32{ i+j+out}则会出错。但是闭包可以。
  • 1
  • 2

闭包也可以作为函数的参数和返回体,下面是闭包作为参数:

fn math<F: Fn() -> i32>(op: F) ->i32 {
	op()
}

fn main() {
	let a = 2;
	let b = 3;
	println!("result is {:?}", math(|| a + b)); //5
	println!("result is {:?}", math(|| a*b)); //6
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

上述定义了函数math,其参数是一个泛型F,并且这个F受到一个Fn() -> i32的trait限定,(trait理解成Java中的interface接口),代表该函数只允许实现Fn()->i32的trait的类型作为参数。
所以Rust中的闭包实际上就是一个匿名结构体和trait来组合实现的。

闭包作为返回值的情况:

fn two_times_impl() -> impl Fn(i32) -> i32 {
	let i = 2;
	move |j| j * i//move关键字将i的所有权转移到闭包中,因为闭包默认是引用,这里是为了防止悬垂指针,后续更新。
}
fn main() {
	let result = two_times_impl();
	println!("result is {:?}", result(2));
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

上述代码中,定义了impl Fn(i32)->i32作为返回值类型,但是在函数定义的时候并不知道具体的返回类型,在函数调用时,编译器会推断出来。

四.流程控制

主要讲条件语句和循环语句

1.条件表达式

之所以叫表达式,就代表其一定有值,而且if表达式的各个分支必须返回同一个类型的值。

fn main() {
	let n = 13;
	let big_n = if (n < 10 && n > -10 ){
		10*n
	} else {
		n/2
	};
	println!("big_n is {:?}", big_n); //6
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

上述代码中,根据逻辑推断出走的else分支,但是13/2应该是6.5,由于变量n默认推断为i32类型,且big_n变量也已经被Rust编译器根据上下文默认推断为i32类型,所以在计算n/2的时候,Rust编译器会将结果截取来符合所推断类型i32。

2.循环表达式

在Rust中,存在三种循环表达式:while,loop,for…in。
下面用for实现FizzBuzz:

fn main() {
	for n in 1..101 {
		if n % 15 == 0 {
			println!("fizzbuzz");
		} else if n % 3 == 0 {
			println!("fizz");
		} else if n % 5 == 0 {
			prinln!("buzz");
		} else {
			prinln!("{}", n);
		}
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
	当使用无限循环的时候Rust可使用loop,避免使用while true
  • 1

3.match表达式

fn main() {
	let number = 42;
	match number {
		0 => println!("zero"),
		1...3 => println!("one to three"),
		5 | 7 | 13 => println!("five or seven or thire"),
		n @ 42 => println!("you can use n in this match,n is {}", n),
		_ => println!("others"),
		}
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这种格式分为左右两个分支,左侧为number所匹配的具体结果,右侧为执行代码,上述代码,当number为0的时候,输出zero,当为数字1-3之间的数字时,执行右侧代码,依次,下面是单独匹配,当是5,7,13之间某一位数字的时候执行右侧代码,@符号为match中的赋值符号,可以把当前值赋值给变量n,供右侧代码使用。最后的下划线_为其余情况统一执行右侧代码。
match表达式左侧为模式,右侧为执行的代码

5.if let和 while let表达式

这两个是在某些情况来代替match表达式的。

fn main() {
	let boolean = true;
	let mut binary = 0;
	if let true = boolean {
		binary = 1;
	}
	assert_eq!(binary, 1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

if let表达式左侧为模式,右侧为具体的值,上述代码及当boolean变量为true的时候,将binary的值从0改为1。
在某些循环场合下,while let可简化代码,循环代码如下:

fn main() {
	let mut v = vec![1,3,5];
	loop {
		match v.pop() {
			Some(x) => println!("{}",x),
			None => break,
		}
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

上述代码中,None => break只负责跳出代码,可简化,改为while let及为:

fn main() {
	let mut v = vec![1,3,5];
	while let Some(x) = v.pop() {
		println!("{}", x);
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

五.基本数据类型

1. 布尔类型

Rust内置了布尔类型,类型名为bool,只有两个值,true和false。

fn main() {
	let x = true;
	assert_eq!(x as i32, 1);// 可以通过as把bool类型转为数字类型,但是不能数字转bool
	let y: bool = false;
	let x = 5;
	if x > 1 {
		println!("x is bigger than 1 ")
	};
	assert_eq!(y as i32, 0);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2.基本数字类型

Rust的基本数字类型可为三类:固定取值范围的类型,动态取值范围的类型,浮点类型

  • 固定取值范围的为无符号整数和符号整数,
    无符号:u8,u16,u32,u64,u128,后面的数字代表占有多少位,比如u8占用8位及一个字节。
    有符号:i8,i16,i32,i64,i128,与无符号一样的含义。
  • 动态取值范围类型:
    usize,占用4个或者8个字节,取决于机器的字长。
    isize,与上诉usize一样。
  • 浮点类型:
    f32,单精度32位浮点数,至少6位有效数字。
    f64,双精度64位浮点数,至少15位有效数字。

3.字符类型

在Rust中,用单引号来定义字符,每个字符占4个字节。

fn main() {
	let x = 'r';
	}
  • 1
  • 2
  • 3

4.数组类型

数组类型的特点:

  • 数组大小是固定的
  • 元素均为同一类型
  • 默认是不可变的
    数组类型的声明方式位[T; N],T为泛型,就是指定为某一特定类型,后续不可变,N为数组的长度。
fn main() {
	let arr: [i32, 3] = [1,2,3];
	let mut mut_arr = [1,2,3];// 绑定为一个可变数组
	assert_eq!(1, arr[0]);
	mut_arr[0] = 3; // 可修改可变数组中的数据
	assert_eq!(3, mut_arr[0]);
	let init_arr = [0; 10];	//创建一个长度为10,初始值为0的数组
	//println!("out index is error :{}", arr[5])编译失败,数组越界。
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

对于越界的数组,Rust会编译报错。
对于原始的数组,只有实现了Copy trait的类型才能作为其元素。

5.范围类型

范围类型包括了左闭右开全闭合两种。

fn main() {
	assert_eq!((1..5), std::ops::Range{start: 1, end: 5});//左闭右开
	assert_eq!((1..=5), std::ops::RangeInclusive::new(1, 5));//全闭合
	assert_eq!(3+4+5, (3..=5).sum());
	assert_eq!(3+4+5, (3..6).sum());
	for i in (1..5) {
		println!("{}", i);// 1,2,3,4
	}
	
	for i in (1..=5) {
		println!("{}", i);// 1,2,3,4,5
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

6.切片类型

切片类型是对一个数组(包括固定大小的数组和动态数组)的引用片段,有利于安全有效的访问数组的一部分,而不需要拷贝。理论上切片的引用是已经存在的变量,在底层,切片代表一个指向数组起始位置的指针和数组长度。用[T]类型表示连续序列,那么切片类型就是&[T]和&mut [T]。

fn main() {
	let arr: [i32, 5] = [1,2,3,4,5];
	assert_eq!(&arr, &[1,2,3,4,5]);
	assert_eq!(&arr[1..], [2,3,4,5]); //表示获取索引1以后的所有元素,索引是从0开始计算的。
	assert_eq!((&arr).len(), 5);
	let arr = &mut [1,2,3];// 定义一个可变切片,可以直接通过索引修改对应的值。
	arr[1] = 7;
	assert_eq!(arr, &[1,7,3]);
	let vec = vec![1,2,3];
	assert_eq!(&vec[..], [1,2,3]);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

7. str字符串类型

Rust存在原始的字符串类型str,通常都是以不可变借用的形式存在,既&str(字符串切片),处于内存安全考虑,Rust将字符串分为两类,一种是固定长度字符串,&str,另一种是可增长字符串,可以随意改变其长度,String

fn main() {

	// 定义一个静态生命周期的str字符串,则truth的生命周期和该程序代码的生命周期是同步的。
	let truth: &'static str = "Rust是一门优雅的语言";
	let ptr = truth.as_ptr();
	let len = truth.len();
	assert_eq!(28, len);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • &str字符串类型由两个部分组成:指向字符串序列的指针和记录长度的值。分别对应的方法为as_ptr和len方法。

8.原生指针

可以表示内存地址的类型被称为指针。Rust中包括了多种指针,引用(reference),原生指针(Raw Pointer),函数指针(fn pointer)和智能指针(Smart Pointer)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Guff_9hys/article/detail/824035
推荐阅读
相关标签
  

闽ICP备14008679号