一. 变量与可变性
1.1. 变量与常量
在Rust中的变量默认是不可变的。可是假如运用mut
关键字能够让变量可变。下面先看一个不可变的比如:
fn main() {
let x = 5;
println!("The value of x is: {}", x);
x = 10; // ERROR
println!("The value of x is: {}", x);
}
此刻是无法经过编译的,履行cargo run会报错:
error[E0384]: cannot assign twice to immutable variable `x`
--> src/main.rs:4:5
|
2 | let x = 5;
| -
| |
| first assignment to `x`
| help: consider making this binding mutable: `mut x`
3 | println!("The value of x is: {}", x);
4 | x = 10;
| ^^^^^^ cannot assign twice to immutable variable // 不能给不可变变量赋值两次
For more information about this error, try `rustc --explain E0384`.
error: could not compile `rust-example` due to previous error
Rust的编译器给了十分详细的过错提示:不能给不可变变量赋值两次。Rust的编译器能够保证那些声明为不可变的值必定不会产生改动。
接着看一下mut
的运用:
fn main() {
let mut x = 5;
println!("The value of x is: {}", x);
x = 10;
println!("The value of x is: {}", x);
}
此刻在履行cargo run
就不会报错了!
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/rust-example`
The value of x is: 5
The value of x is: 10
正是因为mut
呈现了变量绑定的过程中,一切咱们能够合法的将x的值从5改到10。
在变量的可变和不可变可能会让你联想到另一个常见的编程概念:常量。比如:
const MIN_VALUE: i32 = 0; // Rust中预订俗成的运用下划线分隔全大写字母来命名一个常量
fn main() {
const MAX_VALUE: i32 = 10;
}
常量默认是不可变的;在申明的时分有必要显现标明数据类型;常量能够被声明在任何的效果域中,乃至是全局效果域。
1.2. 躲藏
在上一篇文章中,有将String变量转为i32的操作,是用同名变量掩盖旧的变量。
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("读取失败");
let guess: i32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue
};
在Rust中,咱们把这一现象描述为:第一个变量被第二个变量躲藏了。这意味着咱们随后运用这个称号时,它指向的将会是第二个变量。这儿咱们也能够重复运用let关键字并配以相同的称号来不断的躲藏变量
fn main() {
let x = 5;
let x = x + 1;
let x = x * 2;
println!("The value if x is: {}", x);
}
履行编译运转:cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/rust-example`
The value if x is: 12
这儿需求留意躲藏机制和mut的区别:
- 声明变量为mut,不运用let会编译过错。经过let咱们能够对这个值履行一系列的变换操作,并答应这个变量在操作完成后保持自己的不变性。
- 因为重复运用let关键字会创建出新的变量,所以咱们能够复用变量称号的一起改动他的类型。
let x = "hello world";
let x = x.len();
println!("The value if x is: {}", x); // 11
假如运用mut会报错:
let mut x = "hello world";
// let x = x.len(); // OK
x = x.len(); // ERROR
二. 数据类型
Rust中每一个值都有其特定的数据类型,Rust会依据数据类型来决定应该怎么处理它们。Rust是一门静态言语,这意味着它在编译程序的过程中需求知道一切变量的具体类型。在大部分情况下,编译器都能够依据咱们怎么绑定、运用变量的值来主动推导出变量的类型。可是有些时分还是需求咱们显现的标示类型:
let guess1: u32 = "42".parse().expect("Not a number"); // OK
let guess2 = "42".parse().expect("Not a number"); // ERROR
此刻guess2是过错如下:
error[E0282]: type annotations needed
--> src/main.rs:3:9
|
3 | let guess2 = "42".parse().expect("Not a number");
| ^^^^^^
|
help: consider giving `guess2` an explicit type
|
3 | let guess2: _ = "42".parse().expect("Not a number");
| +++
For more information about this error, try `rustc --explain E0282`.
error: could not compile `rust-example` due to previous error
这段信息标明当时的编译器无法主动推导出变量的类型,为了避免混淆,它需求咱们手动增加类型标示。
2.1. 标量类型
标量类型是单个值类型的统称。Rust中内置了4种根底的标量类型:整数、浮点数、布尔值和字符。
2.1.1. 整数类型
整数是指那些没有小数部分的数字。每个长度不同的值都存在有符号和无符号两种变体。
长度 | 有符号 | 无符号 |
---|---|---|
8bit | i8 | u8 |
16bit | i16 | u16 |
32bit | i32 | u32 |
64bit | i64 | u64 |
arch | isize | usize |
有符号和无符号代表了一个整数类型是否具有描述负数的能力。上面的isize/usize两个特别的整数类型,它们的长度取决于程序运转的方针渠道,在64位架构的它们是64位的,而在32位架构上的,它们便是32位的。
下面看一下整数字面量。留意:除了Byte,其余一切字面量都能够运用类型后缀,例如:57u8,代表一个运用了u8类型的整数57,一起也能够运用下划线作为分隔符以便利读数。
整数字面量 | 示例 |
---|---|
Decimal | 98_222 |
Hex | 0xff |
Octal | 0o77 |
Binary | 0b1111_000 |
Byte(u8 only) | b’A’ |
这儿需求留意一点便是整数溢出的问题,Rust在Debug模式下整数溢出会panic,可是在release模式下假如产生整数溢出产生时履行二进制补码盘绕。简单来说,任何超出类型的最大值的数值会被盘绕为类型最小值,例如u8的256会变成0,257会变成1。此刻程序尽管不会panic,可是核算成果就会让人很奇怪了。
2.1.2. 浮点数类型
除了整数,Rust还供给了两种根底的浮点数类型,浮点数便是带小数的数字。这种类型是f32(单精度浮点数)/f64(双精度浮点数).它们别离占用了32位/64位空间。因为在现代CPU中f64与f32的运转功率相差无几,却具有更高的精度,一切在Rust中,默认会将浮点数字面量的类型推到为f64。
let b = 5.6; // f64
一切的数值类型,Rust都支撑常见的数学运算:加、减、乘、除、取余。
2.1.3. 布尔类型
Rust的布尔和其他编程言语相同,布尔类型只支撑两个可能的值:true/false,它占有耽误字节的空间巨细。
let b = false;
布尔类型最主要的用处是在if表达式内作为条件运用。
2.1.4. 字符类型
在Rust中,char类型被用于描述言语中最根底的单个字符。char类型运用单引号指定,而不同于字符串运用双引号指定。
let a = 'a';
let b = '*';
Rust的char占用4个字节。是一个Unicode标量值,这也意味着它能够比ASCII多得多的字符内容。
2.2. 复合类型
复合类型能够将多个不同类型的值组合为一个类型。Rust供给了两种内置的根底复合类型:元组和数组。
2.2.1. 复合类型
元组是一种适当常见的复合类型,他能够将其他不同类型的多个值组合进一个复合类型中。元组还具有一个固定的长度:你无法在声明结束后增加或削减其中的元素数量。
let tup: (i32, bool, f32) = (500, true, 6.5);
元组支撑运用模式匹配来解构元组:
let tup: (i32, bool, f32) = (500, true, 6.5);
let (x, y, z) = tup; // 解构
println!("x: {}, y: {}, z: {}", x, y, z); // x: 500, y: true, z: 6.5
除了解构,咱们还能够经过索引拜访并运用点号(.)来拜访元组的值。元组的索引从0开始。
let tup: (i32, bool, f32) = (500, true, 6.5);
println!("x: {}, y: {}, z: {}", tup.0, tup.1, tup.2); // x: 500, y: true, z: 6.5
2.2.2. 数组类型
数组能够存储多个值的调集。于元组不相同,数组中每一个元素类型都有必要相同。Rust中的数组具有固定的长度,一旦声明就再也不能随意改动巨细。
let a = [1, 2, 3, 4, 5];
let a: [i32, 5] = [1, 2, 3, 4, 5] // i32后边的5表明当时数组包括5个元素
let a = [3; 5] // 这种写法等价于 a = [3, 3, 3, 3, 3]
和这种固定巨细的数组不同Rust规范库还供给了一种更加灵敏的动态数组(vector)类型。动态数组是一种类似于数组的调集类型,可是它答应用户自己调整数组的巨细。
数组由一整块分配在栈上的内存组成,你能够经过索引来拜访一个数组中一切元素:
let a = [1, 2, 3, 4, 5];
let first = a[0];
let last = a[4];
println!("first: {}, last: {}", first, last);
假如拜访数组位置越界,尽管并不会报错,可是运转时会panic。
let last = a[5];
此刻运转会报错:
error: this operation will panic at runtime
--> src/main.rs:4:16
|
4 | let last = a[5];
| ^^^^ index out of bounds: the length is 5 but the index is 5
|
= note: `#[deny(unconditional_panic)]` on by default
error: could not compile `rust-example` due to previous error
三. 函数
函数在Rust中有广泛应用。你应该已经见过Rust最重要的main函数了。Rust代码运用蛇形命名法(只用小写的字母命名,并以下划线分隔单词)来作为规范函数和变量称号的风格。
fn main() {
println!("hello world");
another_function();
}
fn another_function() {
println!("Another function");
}
编译履行:
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/rust-example`
hello world
Another function
3.1. 函数参数
在函数声明中界说参数,它们是一个特别的变量,并被称作为函数签名的一部分。当函数存在参数时,你需求在调用函数时为这些变量供给具体的值。
fn main() {
println!("hello world");
another_function(3);
}
fn another_function(x: i32) {
for i in 0..x {
println!("Another function => {}", i);
}
}
编译履行:
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/rust-example`
hello world
Another function => 0
Another function => 1
Another function => 2
3.2. 函数体中句子与表达式
函数体由若干个句子组成,并能够以一个表达式作为结尾。Rust是一门基于表达式的言语,所以它将句子和表达式区别为两个概念。
- 句子:是指那些履行操作但不返回的指令。
- 表达式:是指绘进行核算并产生一个值作为成果的指令。
看下面的比如:
let x = (let y = 6); // ERROR
因为let y = 6没有任何返回值,所以变量x就没有能够绑定的东西。在看另一个比如:
let y = { // @1
let x = 3;
x + 1 // @2
};
这儿@1是一个代码块。并且最终y=4。这儿的@2是没有分号的,所以@2这段加上分号,代码变成了句子而不会返回任何值。
3.3. 函数的返回值
函数能够向调用它的代码返回值。比如:
fn main() {
println!("hello world");
let result = another_function(3);
println!("result = {}", result); // reuslt = 13
}
fn another_function(x: i32) -> i32 {
x + 10 // 这儿需求留意下 x + 10; 为什么不对的原因
}
编译履行:
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `target/debug/rust-example`
hello world
result = 13
四. 注释
代码注释和平常其他句子注释相同,比如:
fn main() {
// 注释 println!("hello world");
let result = another_function(3);
println!("result = {}", result); // 注释 result = 13
}
五. 控制流
5.1. if表达式
先看一下最常用的运用方式, 条件表达式有必要产生一个bool类型的值。
fn main() {
let number = 3;
if number < 5 { // 这儿是没有()的
println!("condition was true");
} else {
println!("condition was false");
}
}
接着看一下else if多重条件判断,这种情况match更好用。
fn main() {
let number = 3;
if number % 4 == 0 {
println!("number is divisible by 4");
} else if number % 3 == 0 {
println!("number is divisible by 3");
} else if number % 2 == 0 {
println!("number is divisible by 2");
} else {
println!("number is not divisible by 4, 3 or 2");
}
}
在Rust中let能够和if搭配运用,比如:
fn main() {
let condition = true;
let number = if condition { 5 } else { 6 }; // 看着很便利的姿态
println!("number = {}", number);
}
5.2. 循环句子
Rust供给了3种循环:loop、while和for,下面挨个看一下运用方式
5.2.1. loop循环
loop关键字便是一直履行某一块代码,直到显现的声明退出为止。比如:
fn main() {
let mut total = 0;
let result = loop {
total += 1;
if total == 10 {
break total * 2
}
};
println!("result = {}", result);
}
5.2.2. while循环
while循环是一种常见的循环,每次循环都会判断一次条件,条件true持续履行代码片段,条件为false或碰到break就退出当时循环。
fn main() {
let mut total = 0;
while total < 10 {
total += 1;
};
println!("result = {}", total); // result = 10
}
5.2.3. for循环
for循环和其他言语相同,下面看一个简答的遍历数组:
fn main() {
let a = [10, 20, 30, 40, 50];
// 办法一:
for item in a {
println!("{}", item)
}
// 办法二:
for item in a.iter() {
println!("{}", item)
}
// 办法三:
for index in 0..a.len() {
println!("{}", a[index])
}
}
下一章见!