乌龙茶馆

primitive_types1

// primitive_types1.rs
// Fill in the rest of the line that has code missing!
// No hints, there's no tricks, just get used to typing these :)


fn main() {
    // Booleans (`bool`)

    let is_morning = true;
    if is_morning {
        println!("Good morning!");
    }

    let is_evening = true;// Finish the rest of this line like the example! Or make it be false!
    if is_evening {
        println!("Good evening!");
    }
}

简单的布尔型。

primitive_types2

// primitive_types2.rs
// Fill in the rest of the line that has code missing!
// No hints, there's no tricks, just get used to typing these :)
// I AM NOT DONE

fn main() {
    // Characters (`char`)
    let my_first_initial = 'C';
    if my_first_initial.is_alphabetic() {
        println!("Alphabetical!");
    } else if my_first_initial.is_numeric() {
        println!("Numerical!");
    } else {
        println!("Neither alphabetic nor numeric!");
    }
    let your_character = 'N'; // Finish this line like the example! What's your favorite character?
    // Try a letter, try a number, try a special character, try a character
    // from a different language than your own, try an emoji!
    if your_character.is_alphabetic() {
        println!("Alphabetical!");
    } else if your_character.is_numeric() {
        println!("Numerical!");
    } else {
        println!("Neither alphabetic nor numeric!");
    }
}

自定义个your_character。

primitive_types3

// primitive_types3.rs
// Create an array with at least 100 elements in it where the ??? is.
// Execute `rustlings hint primitive_types3` for hints!

// I AM NOT DONE

fn main() {
    let a = ???

    if a.len() >= 100 {
        println!("Wow, that's a big array!");
    } else {
        println!("Meh, I eat arrays like that for breakfast.");
    }
}

这里涉及到的概念是数组的定义。Rust中的数组是固定长度的,一整块分配在栈上的内存。一旦声明,它们的长度不能增长或缩小。平时访问数组是通过索引访问,这个和其他语言差不多。

数组标记为[T; size] T为元素类型,size表示数组的大小。

primitive_types4

// primitive_types4.rs
// Get a slice out of Array a where the ??? is so that the test passes.
// Execute `rustlings hint primitive_types4` for hints!!

#[test]
fn slice_out_of_array() {
    let a = [1, 2, 3, 4, 5];

    let nice_slice = ???

    assert_eq!([2, 3, 4], nice_slice)
}

这里涉及到一个没有所有权的类型:Slice类型。Slice允许你引用集合中一段连续的元素序列,而不用引用整个集合。切片的类型和数组类似,但其大小在编译时是不确定的。相反,切片是一个双字对象。第一个字是一个指向数据的指针,第二个字是切片的长度。这个”字”的宽度和usize相同,由处理器架构决定,比如在x86-64平台上就是64位。Slice 的类型标记为 &[T]。

Slice有很多种类型,先来说一说字符串Slice

字符串Slice是String中一部分值的引用。引用的概念极其重要,后面也会专门讲到。它看起来像这样:

let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];

可以使用一个由中括号中的[starting_index..ending_index]指定的range创建一个Slice,其中starting_index是Slice的第一个位置,ending_index则是Slice最后一个位置的后一个值。在其内部,Slice的数据结构存储了slice的开始位置和长度,长度对应于ending_index减去starting_index的值。所以对于let world = &s[6..11]的情况,world将是一个包含指向s第7个字节(从1开始)的指针和长度值5的slice。

对于Rust的“..”语法,如果想要从第一个索引(0)开始,可以不写两个点号之前的值。

let s = String::from("hello");

let slice = &s[0..2];
let slice = &s[..2];

上面两个语句是完全相同的。依此类推,如果Slice包含String的最后一个字节,也可以舍弃尾部的数字。这里不再过多赘述。

之前我们说过字符串字面值被储存在二进制文件中,现在我们可以重新理解一下字符串字面值。

let s = "Hello, world!";

这里s的类型是&str:它是一个指向二进制程序特定位置的Slice。这也就是为什么字符串字面值是不可变的;&str是一个不可变引用。

在知道了能够获取字面值和String的Slice后,我们可以把他们作为参数传入函数。如

fn first_word(s: &str) -> &str {

如果有一个字符串Slice,则可以直接传递它。如果有一个String,则可以传递整个String 的Slice。

下面是数组和Slice的相关例子,字符串Slice的类型声明写作&str,不过下面这个例子用的是更通用的Slice类型,即&[i32]。它跟字符串Slice的工作方式一样,通过存储第一个集合元素的引用和一个集合总长度。我们也可以对其他所有集合使用这类Slice

use std::mem;  //验证栈上内存引入的模块
//此函数借用一个slice
fn analyze_slice(slice: &[i32]) {
    println!("first element of the slice: {}", slice[0]);
    println!("the slice has {} elements", slice.len());
}
fn main() {
    //定长数组(类型标记是多余的)
    let xs: [i32; 5] = [1, 2, 3, 4, 5];
    //所有元素可以初始化成相同的值
    let ys: [i32; 500] = [0; 500];
    //下标从 0 开始
    println!("first element of the array: {}", xs[0]);
    println!("second element of the array: {}", xs[1]);
    //`len` 返回数组的大小
    println!("array size: {}", xs.len());
    //数组是在栈中分配的
    println!("array occupies {} bytes", mem::size_of_val(&xs));
    //数组可以自动被借用成为 slice
    println!("borrow the whole array as a slice");
    analyze_slice(&xs);
    //slice可以指向数组的一部分
    println!("borrow a section of the array as a slice");
    analyze_slice(&ys[1 .. 4]);
    //越界的下标会引发致命错误(panic)
    //println!("{}", xs[5]);

输出:

first element of the array: 1
second element of the array: 2
array size: 5
array occupies 20 bytes
borrow the whole array as a slice
first element of the slice: 1
the slice has 5 elements
borrow a section of the array as a slice
first element of the slice: 0
the slice has 3 elements

所以,将???那里替换为

let nice_slice = &a[1..4];

primitive_types5

// primitive_types5.rs
// Destructure the `cat` tuple so that the println will work.
// Execute `rustlings hint primitive_types5` for hints!

// I AM NOT DONE
fn main() {
    let cat = ("Furry McFurson", 3.5);
    let /* your pattern here */ = cat;

    println!("{} is {} years old.", name, age);
}

这里涉及到了元组以及元组的解构。

前面在第一节简单提过元组属于复合类型,它是一个将多个其他类型的值组合进一个复合类型的主要方式。元组长度固定:一旦声明,其长度不会增大或缩小。所以也不会涉及到所有权的问题。我们使用包含在圆括号中的逗号分隔的值列表来创建一个元组。元组中的每一个位置都有一个类型,而且这些不同值的类型也不必是相同的。cat内部包含String字面值和f64两种类型。

为了从元组中获取单个值,我们可以使用模式匹配(pattern matching)来解构(destructure)元组值。除了使用模式匹配解构外,也可以使用点号(.)后跟值的索引来直接访问。

下面是元组的一些输入输出的例子,方便理解。

//元组可以充当函数的参数和返回值
fn reverse(pair: (i32, bool)) -> (bool, i32) {
    //可以使用let把一个元组的成员绑定到一些变量
    let (integer, boolean) = pair;
    (boolean, integer)
}

#[derive(Debug)]
struct Matrix(f32, f32, f32, f32);
fn main() {
    // 包含各种不同类型的元组
    let long_tuple = (1u8, 2u16, 3u32, 4u64,
                      -1i8, -2i16, -3i32, -4i64,
                      0.1f32, 0.2f64,
                      'a', true);

    //可以通过元组的下标来访问具体的值
    println!("long tuple first value: {}", long_tuple.0);
    println!("long tuple second value: {}", long_tuple.1);
    //元组也可以充当元组的元素
    let tuple_of_tuples = ((1u8, 2u16, 2u32), (4u64, -1i8), -2i16);
    //元组可以打印
    println!("tuple of tuples: {:?}", tuple_of_tuples);
    // 但很长的元组无法打印
    // let too_long_tuple = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13);
    // println!("too long tuple: {:?}", too_long_tuple);
    // 试一试 ^ 取消上面两行的注释,阅读编译器给出的错误信息。
    let pair = (1, true);
    println!("pair is {:?}", pair);
    println!("the reversed pair is {:?}", reverse(pair));
    // 创建单元素元组需要一个额外的逗号,这是为了和被括号包含的字面量作区分。
    println!("one element tuple: {:?}", (5u32,));
    println!("just an integer: {:?}", (5u32));
    // 元组可以被解构(deconstruct),从而将值绑定给变量
    let tuple = (1, "hello", 4.5, true);
    let (a, b, c, d) = tuple;
    println!("{:?}, {:?}, {:?}, {:?}", a, b, c, d);
    let matrix = Matrix(1.1, 1.2, 2.1, 2.2);
    println!("{:?}", matrix)
}

输出:

long tuple first value: 1
long tuple second value: 2
tuple of tuples: ((1, 2, 2), (4, -1), -2)
pair is (1, true)
the reversed pair is (true, 1)
one element tuple: (5,)
just an integer: 5
1, "hello", 4.5, true
Matrix(1.1, 1.2, 2.1, 2.2)

回到题目,我们添加下列语句即可。

let (name, age) = cat;

即可完成解构。

在例子中,我添加了Debug注解,后续会讲到。

primitive_types6


// primitive_types6.rs
// Use a tuple index to access the second element of `numbers`.
// You can put the expression for the second element where ??? is so that the test passes.
// Execute `rustlings hint primitive_types6` for hints!

#[test]
fn indexing_tuple() {
    let numbers = (1, 2, 3);
    // Replace below ??? with the tuple indexing syntax.
    let second = ???;

    assert_eq!(2, second,
        "This is not the 2nd number in the tuple!")
}

这里即考察了元组的第二种访问方式:通过索引值访问。

let second = numbers.1; //Index

补充一下Rust的四则运算。

其中整数1、浮点数1.2、字符’a’、字符串”abc”、布尔值true和单元类型()可以用数字、文字或符号之类的字面量(literal)来表示。另外,通过加前缀0x、0o、0b,数字可以用十六进制、八进制或二进制记法表示。为了改善可读性,可以在数值字面量中插入下划线,比如:1_000等同于1000,0.000_001等同于0.000001。

//整数相加
println!("1 + 2 = {}", 1u32 + 2);
//整数相减
println!("1 - 2 = {}", 1i32 - 2);
//短路求值的布尔逻辑
println!("true AND false is {}", true && false);
println!("true OR false is {}", true || false);
println!("NOT true is {}", !true);
//位运算
println!("0011 AND 0101 is {:04b}", 0b0011u32 & 0b0101);
println!("0011 OR 0101 is {:04b}", 0b0011u32 | 0b0101);
println!("0011 XOR 0101 is {:04b}", 0b0011u32 ^ 0b0101);
println!("1 << 5 is {}", 1u32 << 5);
println!("0x80 >> 2 is 0x{:x}", 0x80u32 >> 2);
//使用下划线改善数字的可读性
println!("One million is written as {}", 1_000_000u32);

输出:

1 + 2 = 3
1 - 2 = -1
true AND false is false
true OR false is true
NOT true is false
0011 AND 0101 is 0001
0011 OR 0101 is 0111
0011 XOR 0101 is 0110
1 << 5 is 32
0x80 >> 2 is 0x20
One million is written as 1000000

Rust还提供了多种机制,用于改变或定义原生类型和用户定义类型。Rust不提供原生类型之间的隐式类型转换,但可以使用as关键字进行显式类型转换。整型之间的转换大体遵循C语言的惯例,但是C会产生未定义行为的情形,而在Rust中所有整型转换都是定义良好的。

Rust通过type进行重命名。下面是进行类型转换,类型推断和重命名的样例,有些后续还会涉及。

// 不显示类型转换产生的溢出警告。
#![allow(overflowing_literals)]

// `NanoSecond` 是 `u64` 的新名字。
type NanoSecond = u64;
type Inch = u64;

fn main() {
    let decimal = 65.4321_f32;

    //错误!不提供隐式转换
    //let integer: u8 = decimal;

    //可以显式转换
    let integer = decimal as u8;
    let character = integer as char;
    println!("Casting: {} -> {} -> {}", decimal, integer, character);

    //当把任何类型转换为无符号类型T时,会不断加上或减去(std::T::MAX + 1)
    //直到值位于新类型T的范围内。

    //1000 已经在 u16 的范围内
    println!("1000 as a u16 is: {}", 1000 as u16);

    //1000 - 256 - 256 - 256 = 232
    // 事实上的处理方式是:从最低有效位(LSB,least significant bits)开始保留
    // 8 位,然后剩余位置,直到最高有效位(MSB,most significant bit)都被抛弃。
    // 译注:MSB 就是二进制的最高位,LSB 就是二进制的最低位,按日常书写习惯就是
    // 最左边一位和最右边一位。
    println!("1000 as a u8 is : {}", 1000 as u8);
    // -1 + 256 = 255
    println!("  -1 as a u8 is : {}", (-1i8) as u8);

    // 对正数,这就和取模一样。
    println!("1000 mod 256 is : {}", 1000 % 256);

    // 当转换到有符号类型时,(位操作的)结果就和 “先转换到对应的无符号类型,
    // 如果 MSB 是 1,则该值为负” 是一样的。

    // 当然如果数值已经在目标类型的范围内,就直接把它放进去。
    println!(" 128 as a i16 is: {}", 128 as i16);
    // 128 转成 u8 还是 128,但转到 i8 相当于给 128 取八位的二进制补码,其值是:
    println!(" 128 as a i8 is : {}", 128 as i8);

    // 重复之前的例子
    // 1000 as u8 -> 232
    println!("1000 as a u8 is : {}", 1000 as u8);
    // 232 的二进制补码是 -24
    println!(" 232 as a i8 is : {}", 232 as i8);


    //对数值字面量,只要把类型作为后缀加上去,就完成了类型说明。
    //比如指定字面量 42 的类型是 i32,只需要写 42i32。
    //无后缀的数值字面量,其类型取决于怎样使用它们。如果没有限制,
    //编译器会对整数使用 i32,对浮点数使用 f64。
    //带后缀的字面量,其类型在初始化时已经知道了。
    let x = 1u8;
    let y = 2u32;
    let z = 3f32;

    //无后缀的字面量,其类型取决于如何使用它们。
    let i = 1;
    let f = 1.0;

    // `size_of_val` 返回一个变量所占的字节数
    //std::mem::size_of_val 是一个函数,这里使用其完整路径(full path)调用。
    //代 码可以分成一些叫做模块(module)的逻辑单元。
    //在本例中,size_of_val 函数是 在 mem 模块中定义的,而 mem 模块又是在 std crate 中定义的。
    println!("size of `x` in bytes: {}", std::mem::size_of_val(&x)); //传引用
    println!("size of `y` in bytes: {}", std::mem::size_of_val(&y));
    println!("size of `z` in bytes: {}", std::mem::size_of_val(&z));
    println!("size of `i` in bytes: {}", std::mem::size_of_val(&i));
    println!("size of `f` in bytes: {}", std::mem::size_of_val(&f));

    //Rust的类型推断引擎是很聪明的,它不只是在初始化时看看右值(r-value)的类型而已,
    //它还会考察变量之后会怎样使用,借此推断类型。这是一个类型推导的进阶例子:

    // 因为有类型说明,编译器知道 `elem` 的类型是 u8。
    let elem = 5u8;
    //创建一个空向量(vector,即不定长的,可以增长的数组)。
    let mut vec = Vec::new();
    //现在编译器还不知道 `vec` 的具体类型,只知道它是某种东西构成的向量(`Vec<_>`)
    //在向量中插入 `elem`。
    vec.push(elem);
    //啊哈!现在编译器知道 `vec` 是 u8 的向量了(`Vec<u8>`)。
    println!("{:?}", vec);

    //可以用 type 语句给已有的类型取个新的名字。
    //类型的名字必须遵循驼峰命名法(像是 CamelCase 这样)
    //否则编译器将给出错误。原生类型是例外,比如: usize、f32,等等。
    // `NanoSecond` = `Inch` = `u64_t` = `u64`.
    let nanoseconds: NanoSecond = 5 as u64_t;
    let inches: Inch = 2 as u64_t;

    // 注意类型别名*并不能*提供额外的类型安全,因为别名*并不是*新的类型。
    println!("{} nanoseconds + {} inches = {} unit?",
            nanoseconds,
            inches,
            nanoseconds + inches);
    //别名的主要用途是避免写出冗长的模板化代码(boilerplate code)。如 IoResult<T> 是 Result<T, IoError> 类型的别名。

}