一. 变量与可变性

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])
    }
}

下一章见!