Rust 代码片段:排序vec

原文:https://rust-lang-nursery.github.io/rust-cookbook/algorithms/sorting.html

排序整数

可以通过 vec::sort 排序,也可以使用 vec::sort_unstable 它的速度更快,但相等的元素不保证顺序。

fn main() {
    let mut vec = vec![1, 5, 10, 2, 15];

    vec.sort();

    assert_eq!(vec, vec![1, 2, 5, 10, 15]);
}

排序浮点数

fn main() {
    let mut vec = vec![1.1, 1.15, 5.5, 1.123, 2.0];

    vec.sort_by(|a, b| a.partial_cmp(b).unwrap());

    assert_eq!(vec, vec![1.1, 1.123, 1.15, 2.0, 5.5]);
}

排序结构体

使结构体能够排序(vec::sort()),需要使用 Eq, PartialEq, Ord, PartialOrd 四个特性;自定义排序用vec::sort_by

#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
struct Person {
    name: String,
    age: u32
}

impl Person {
    pubfn new(name: String, age: u32) -> Self {
        Person {
            name,
            age
        }
    }
}

fn main() {
    let mut people = vec![
        Person::new("Zoe".to_string(), 25),
        Person::new("Al".to_string(), 60),
        Person::new("John".to_string(), 1),
    ];

    // Sort people by derived natural order (Name and age)
    people.sort();

    assert_eq!(
        people,
        vec![
            Person::new("Al".to_string(), 60),
            Person::new("John".to_string(), 1),
            Person::new("Zoe".to_string(), 25),
        ]);

    // Sort people by age
    people.sort_by(|a, b| b.age.cmp(&a.age));

    assert_eq!(
        people,
        vec![
            Person::new("Al".to_string(), 60),
            Person::new("Zoe".to_string(), 25),
            Person::new("John".to_string(), 1),
        ]);

}

Rust总结:复合类型-字符串

原文:https://course.rs/basic/compound-type/string-slice.html

  • #![allow(unused_variables)] 编译器忽略未使用变量的警告
  • unimplemented!() 指明函数没有实现

切片

对于字符串而言,切片就是对 String 类型中某一部分的引用,String类型的切片就是&str

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

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

在对字符串使用切片语法时需要格外小心,切片的索引必须落在字符之间的边界位置,也就是 UTF-8 字符的边界,例如中文在 UTF-8 中占用三个字节,下面的代码就会崩溃:

let s = "中国人";
let a = &s[0..2];
println!("{}",a);

👇 错误,在获得了不可变借用后,使用可变借用,然后又打印先前的不可变借用产生错误,两者不能共存。

fn main() {
    let mut s = String::from("hello world");

    let word = first_word(&s); // 获得不可用借用

    s.clear(); // error! // 获得可变借用

    println!("the first word is: {}", word); // 打印不可变借用
}
fn first_word(s: &String) -> &str {
    &s[..1]
}

修复上面的代码👇

fn main() {
    let mut s = String::from("hello world");

    // 这里, &s 是 `&String` 类型,但是 `first_word` 函数需要的是 `&str` 类型。
    // 尽管两个类型不一样,但是代码仍然可以工作,原因是 `&String` 会被隐式地转换成 `&str` 类型,如果大家想要知道更多,可以看看 Deref 章节: https://course.rs/advance/smart-pointer/deref.html
    let word = first_word(&s);

    println!("the first word is: {}", word);
    // 关键是调整了word的使用位置
    s.clear();
}
fn first_word(s: &str) -> &str {
    &s[..1]
}

其他切片

#![allow(unused)]
fn main() {
    let a = [1, 2, 3, 4, 5];

    let copy = a; // 复制,地址与a不同let slice1 = &a[..];
    let slice2 = &a[..2];

    println!("{:p}, {:p}, {:p}, {:p}", &a, &copy, slice1, slice2)
    // 0x7ffee4da2cf0, 0x7ffee4da2d04, 0x7ffee4da2cf0, 0x7ffee4da2cf0
}

一个切片引用占用了2个大小的内存空间( 从现在开始,为了简洁性考虑,如无特殊原因,我们统一使用切片来特指切片引用 )。 该切片的第一个字是指向数据的指针,第二个字是切片的长度。字的大小取决于处理器架构,例如在 x86-64 上,字的大小是 64 位也就是 8 个字节,那么一个切片引用就是 16 个字节大小

  • 切片签名 &[T]
  • 数组签名 [T; length]
fn main() {
    let arr: [char; 3] = ['中', '国', '人'];

    let slice = &arr[..2];

    // 修改数字 `8` 让代码工作
    // 小提示: 切片和数组不一样,它是引用。如果是数组的话,那下面的 `assert!` 将会通过: '中'和'国'是char类型,char类型是Unicode编码,大小固定为4字节,两个字符为8字节。
    assert!(std::mem::size_of_val(&slice) == 16);
}

字符串

⭐️ 字符串字面量切片

虽然 String 的底层是 Vec<u8> 也就是字节数组的形式存储的,但是它是基于 UTF-8 编码的字符序列。String 分配在堆上、可增长且不是以 null 结尾。

而 &str 是切片引用类型( &[u8] ),指向一个合法的 UTF-8 字符序列,总之,&str 和 String 的关系类似于 &[T] 和 Vec<T> 。

let s = "Hello, world!";
// ->let s: &str = "Hello, world!";
// s是不可变引用

str 类型是硬编码进可执行文件,也无法被修改,但是 String 则是一个可增长、可改变且具有所有权的 UTF-8 编码字符串,当 Rust 用户提到字符串时,往往指的就是 String 类型和 &str 字符串切片类型,这两个类型都是 UTF-8 编码。

String 与 &str 的转换

&str to String

  • String::from("hello,world")
  • "hello,world".to_string()

String to &str:

  • 取引用(切片)let slice1 = &s;
  • let slice1: &str = s.as_str();
fn main() {
    let s = String::from("hello,world!");
    say_hello(&s);
    say_hello(&s[..]);
    say_hello(s.as_str());
}

fn say_hello(s: &str) {
    println!("{}",s);
}

⭐️ Rust 不允许索引字符串

👇 报错

#![allow(unused)]
fn main() {
   let s1 = String::from("hello");
   let h = s1[0];
}

对字符串切片是危险的

操作字符串

追加(String可用)
push(char)/push_str(&str)

插入(String可用)
insert(idx: usize, char)/insert_str(idx: usize, &str)

替换(String/&str可用)
replace(needle: &str, haystack: &str)/replacen(needle: &str, haystack: &str, n)

替换范围(String可用)
replace_range(range, &str)

fn main() {
    let mut string_replace_range = String::from("I like rust!");
    string_replace_range.replace_range(7..8, "R");
    dbg!(string_replace_range);
}

删除(String可用)

  • pop – 删除并返回字符串的最后一个字符。其返回值是一个 Option 类型,如果字符串为空,则返回 None
  • remove —— 删除并返回字符串中指定位置的字符,remove() 方法是按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误。
fn main() {
    let mut string_remove = String::from("测试remove方法");
    println!(
        "string_remove 占 {} 个字节",
        std::mem::size_of_val(string_remove.as_str())
    );
    // 删除第一个汉字
    string_remove.remove(0);
    // 下面代码会发生错误
    // string_remove.remove(1);
    // 直接删除第二个汉字
    // string_remove.remove(3);
    dbg!(string_remove);
}
  • truncate —— 删除字符串中从指定位置开始到结尾的全部字符,无返回值
  • clear —— 清空字符串,相当于truncate(0)

连接字符串

  • 用 + 或 +=s = s1 + &s2 (s, s1, s2都是 String,&s2自动解引用为 &str 类型)
fn main() {
    let string_append = String::from("hello ");
    let string_rust = String::from("rust");
    // &string_rust会自动解引用为&str
    let result = string_append + &string_rust;
    let mut result = result + "!";
    result += "!!!";

    println!("连接字符串 + -> {}", result);
}

⚠️ 注意,之所以可以使用 + 连接字符串是因为,调用了 std::string 标准库中的 add() 方法,这里 add() 方法的第二个参数是一个引用的类型。因此我们在使用 +, 必须传递切片引用类型。不能直接传递 String 类型。+ 和 += 都是返回一个新的字符串。所以变量声明可以不需要 mut 关键字修饰

⚠️ add() 定义

fn add(self, s: &str) -> String

因此

fn main() {
    let s1 = String::from("hello,");
    let s2 = String::from("world!");
    // 在下句中,s1的所有权被转移走了,因此后面不能再使用s1
    let s3 = s1 + &s2;
    assert_eq!(s3,"hello,world!");
    // 下面的语句如果去掉注释,就会报错
    // println!("{}",s1);
}
  • 使用 format! 连接字符串
fn main() {
    let s1 = "hello";
    let s2 = String::from("rust");
    let s = format!("{} {}!", s1, s2);
    println!("{}", s);
}

转义

使用 \

fn main() {
    // 通过 \ + 字符的十六进制表示,转义输出一个字符
    let byte_escape = "I'm writing \x52\x75\x73\x74!";
    println!("What are you doing\x3F (\\x3F means ?) {}", byte_escape);

    // \u 可以输出一个 unicode 字符
    let unicode_codepoint = "\u{211D}";
    let character_name = "\"DOUBLE-STRUCK CAPITAL R\"";

    println!(
        "Unicode character {} (U+211D) is called {}",
        unicode_codepoint, character_name
    );

    // 换行了也会保持之前的字符串格式
    let long_string = "String literals
                        can span multiple lines.
                        The linebreak and indentation here ->\
                        <- can be escaped too!";
    println!("{}", long_string);
}

禁止转义 r"...",包含双引号 r#"..."#

fn main() {
    println!("{}", "hello \\x52\\x75\\x73\\x74");
    let raw_str = r"Escapes don't work here: \x3F \u{211D}";
    println!("{}", raw_str);

    // 如果字符串包含双引号,可以在开头和结尾加 #
    let quotes = r#"And then I said: "There is no escape!""#;
    println!("{}", quotes);

    // 如果还是有歧义,可以继续增加,没有限制
    let longer_delimiter = r###"A string with "# in it. And even "##!"###;
    println!("{}", longer_delimiter);
}

操作UTF-8字符串

遍历 char

for c in "中国人".chars() {
    println!("{}", c);
}

遍历 byte(字节)

for b in "中国人".bytes() {
    println!("{}", b);
}

取子字符串:utf8_slice

习题

如果要使用 str 类型,只能配合 Box。 & 可以用来将 Box 转换为 &str 类型

fn main() {
    let s: Box<str> = "hello, world".into();
    greetings(s)
}

fn greetings(s: Box<str>) {
    println!("{}", &s)
}

fn main() {
    let s: Box<&str> = "hello, world".into();
    greetings(*s)
}

fn greetings(s: &str) {
    println!("{}", s);
}

字节字符串 (Byte String)

use std::str;

fn main() {
    // 注意,这并不是 `&str` 类型了!
    let bytestring: &[u8; 21] = b"this is a byte string";


    // 字节数组没有实现 `Display` 特征,因此只能使用 `Debug` 的方式去打印
    println!("A byte string: {:?}", bytestring);

    // 字节数组也可以使用转义
    let escaped = b"\x52\x75\x73\x74 as bytes";
    // ...但是不支持 unicode 转义
    // let escaped = b"\u{211D} is not allowed";
    println!("Some escaped bytes: {:?}", escaped);


    // raw string
    let raw_bytestring = br"\u{211D} is not escaped here";
    println!("{:?}", raw_bytestring);

    // 将字节数组转成 `str` 类型可能会失败
    if let Ok(my_str) = str::from_utf8(raw_bytestring) {
        println!("And the same as text: '{}'", my_str);
    }

    let _quotes = br#"You can also use "fancier" formatting, \
                    like with normal raw strings"#;

    // 字节数组可以不是 UTF-8 格式
    let shift_jis = b"\x82\xe6\x82\xa8\x82\xb1\x82\xbb"; // "ようこそ" in SHIFT-JIS

    // 但是它们未必能转换成 `str` 类型
    match str::from_utf8(shift_jis) {
        Ok(my_str) => println!("Conversion successful: '{}'", my_str),
        Err(e) => println!("Conversion failed: {:?}", e),
    };
}

练习:

// 填空
fn main() {
    let mut s = String::new();
    __;

    let v = vec![104, 101, 108, 108, 111];

    // 将字节数组转换成 String
    let s1 = __;


    assert_eq!(s, s1);

    println!("Success!")
}

答案

// FILL in the blanks
fn main() {
    let mut s = String::new();
    s.push_str("hello");

    // some bytes, in a vector
    let v = vec![104, 101, 108, 108, 111];

    // Turn a bytes vector into a String
    // We know these bytes are valid, so we'll use `unwrap()`.
    let s1 = String::from_utf8(v).unwrap();


    assert_eq!(s, s1);

    println!("Success!")
}

utf8_slice

use utf8_slice;
fn main() {
    let s = "The 🚀 goes to the 🌑!";

    let rocket = utf8_slice::slice(s, 4, 5);
    // 结果是 "🚀"
}

参考

https://doc.rust-lang.org/std/string/struct.String.html

Rust 代码片段:生成随机数据

原文:https://rust-lang-nursery.github.io/rust-cookbook/algorithms/randomness.html

随机数字

use rand::Rng;

fn main() {
    letmut rng = rand::thread_rng();

    let n1: u8 = rng.gen();
    let n2: u16 = rng.gen();
    println!("Random u8: {}", n1);
    println!("Random u16: {}", n2);
    println!("Random u32: {}", rng.gen::<u32>());
    println!("Random i32: {}", rng.gen::<i32>());
    println!("Random float: {}", rng.gen::<f64>());
}

一定范围内的随机数

use rand::Rng;

fn main() {
    letmut rng = rand::thread_rng();
    println!("Integer: {}", rng.gen_range(0..10));
    println!("Float: {}", rng.gen_range(0.0..10.0));
}

下面这种在重复生成某一范围内的随机数方面效果一样,但速度更快。

use rand::distributions::{Distribution, Uniform};

fn main() {
    letmut rng = rand::thread_rng();
    let die = Uniform::from(1..7);

    loop {
        let throw = die.sample(&mut rng);
        println!("Roll the die: {}", throw);
        if throw == 6 {
            break;
        }
    }
}

用给定的分布生成随机数

use rand_distr::{Distribution, Normal, NormalError};
use rand::thread_rng;

fn main() -> Result<(), NormalError> {
    letmut rng = thread_rng();
    let normal = Normal::new(2.0, 3.0)?;
    let v = normal.sample(&mut rng);
    println!("{} is from a N(2, 9) distribution", v);
    Ok(())
}

用指定类型生成随机值

use rand::Rng;
use rand::distributions::{Distribution, Standard};

#[derive(Debug)]struct Point {
    x: i32,
    y: i32,
}

impl Distribution<Point> for Standard {
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Point {
        let (rand_x, rand_y) = rng.gen();
        Point {
            x: rand_x,
            y: rand_y,
        }
    }
}

fn main() {
    letmut rng = rand::thread_rng();
    let rand_tuple = rng.gen::<(i32, bool, f64)>();
    let rand_point: Point = rng.gen();
    println!("Random tuple: {:?}", rand_tuple);
    println!("Random Point: {:?}", rand_point);
}

用字母表生成随机密码

use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;

fn main() {
    let rand_string: String = thread_rng()
        .sample_iter(&Alphanumeric)
        .take(30)
        .map(char::from)
        .collect();

    println!("{}", rand_string);
}

用自定义字符范围生成随机密码

fn main() {
    use rand::Rng;
    const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
                            abcdefghijklmnopqrstuvwxyz\
                            0123456789)(*&^%$#@!~";
    const PASSWORD_LEN: usize = 30;
    letmut rng = rand::thread_rng();

    let password: String = (0..PASSWORD_LEN)
        .map(|_| {
            let idx = rng.gen_range(0..CHARSET.len());
            CHARSET[idx] aschar
        })
        .collect();

    println!("{:?}", password);
}

Rust 总结:所有权/借用

原文:https://course.rs/basic/ownership/index.html

所有权

  • Rust 中每一个值都被一个变量所拥有,该变量被称为值的所有者
  • 一个值同时只能被一个变量所拥有,或者说一个值只能拥有一个所有者
  • 当所有者(变量)离开作用域范围时,这个值将被丢弃(drop)

实现了Copy Trait的类型(基本类型)

  • 所有整数类型,比如 u32。
  • 布尔类型,bool,它的值是 true 和 false。
  • 所有浮点数类型,比如 f64。
  • 字符类型,char。
  • 元组,当且仅当其包含的类型也都是 Copy 的时候。比如,(i32, i32) 是 Copy 的,但 (i32, String) 就不是。
  • 不可变引用 &T ,例如转移所有权中的最后一个例子,但是注意: 可变引用 &mut T 是不可以 Copy的
fn main() {
    let x: &str = "hello, world";
    // 所有权没有转移,只是y复制了x的引用地址
    let y = x;
    println!("{},{}",x,y);
}

借用

获取变量的引用,称之为借用(borrowing)

解引用:let x = 5;let y = &x; *y 来解出引用所指向的值(也就是解引用)

不可变引用

fn main() {
    let s1 = String::from("hello");

    let len = calculate_length(&s1);

    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

ref

fn main() {
    let c = '中';

    let r1 = &c;
    // fill the blank,dont change other code
    let ref r2 = c;

    assert_eq!(*r1, *r2);

    // check the equality of the two address strings
    assert_eq!(get_addr(r1),get_addr(r2));
}

// get memory address string
fn get_addr(r: &char) -> String {
    format!("{:p}", r)
}

可变引用

fn main() {
    let mut s = String::from("hello");

    change(&mut s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

同一作用域,特定数据只能有一个可变引用

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

let r1 = &mut s;
// r1 作用域已经结束
let r2 = &mut s;

println!("{}, {}", r1, r2); // 报错

可变引用与不可变引用不能同时存在

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

let r1 = &s; // 没问题
let r2 = &s; // 没问题
let r3 = &mut s; // 大问题

println!("{}, {}, and {}", r1, r2, r3); // 因为后续用到了r1,r2

NLL:Non-Lexical Lifetimes(NLL) 专门用于找到某个引用在作用域(})结束前就不再被使用的代码位置。

悬垂引用(Dangling References)

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s // 错误
}

总结

  • 同一时刻,你只能拥有要么一个可变引用, 要么任意多个不可变引用
  • 引用必须总是有效的

Rust总结:基本类型 Primitives

原文:https://rustwiki.org/zh-CN/rust-by-example/primitives.html

标量类型(scalar type)

  • 有符号整数(signed integers):i8、i16、i32、i64、i128 和 isize(指针宽度)
  • 无符号整数(unsigned integers): u8、u16、u32、u64、u128 和 usize(指针宽度)
  • 浮点数(floating point): f32、f64
  • char(字符):单个 Unicode 字符,如 ‘a’,’α’ 和 ‘∞’(每个都是 4 字节)
  • bool(布尔型):只能是 true 或 false
  • 单元类型(unit type):()。其唯一可能的值就是 () 这个空元组
  • 尽管单元类型的值是个元组,它却并不被认为是复合类型,因为并不包含多个值。
fn main() {
    // 整数相加
    println!("1 + 2 = {}", 1u32 + 2);

    // 整数相减
    println!("1 - 2 = {}", 1i32 - 2);
    // 试一试 ^ 尝试将 `1i32` 改为 `1u32`,体会为什么类型声明这么重要

    // 短路求值的布尔逻辑
    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);
}

复合类型(compound type)

  • 数组(array):如 [1, 2, 3]
  • 元组(tuple):如 (1, true)
// 元组可以充当函数的参数和返回值
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)

}

数组和切片

  • 数组(array)
    • 是一组拥有相同类型 T 的对象的集合
      • 内存中是连续存储的
      • 数组使用中括号 [] 来创建
      • 且它们的大小在编译时会被确定
      • 数组的类型标记为 [T; length](译注:T 为元素类型,length 表示数组大小)
  • 切片(slice)
    • 类似数组
    • 但其大小在编译时是不确定的。
    • 切片是一个双字对象(two-word object),第一个字是一个指向数据的指针,第二个字是切片的长度。这个 “字” 的宽度和 usize 相同,由
    • 类型标记为 &[T]
// 此函数借用一个 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]);
}

Rust 总结:格式化

https://github.com/mattwhatsup/rust-practice-and-notes/tree/main/print-and-format
https://rustwiki.org/zh-CN/rust-by-example/hello/print.html

相关模块

std::fmt

  • format!:将格式化文本写到字符串。
  • print!:与 format! 类似,但将文本输出到控制台(io::stdout)。
  • println!: 与 print! 类似,但输出结果追加一个换行符。
  • eprint!:与 print! 类似,但将文本输出到标准错误(io::stderr)。
  • eprintln!:与 eprint! 类似,但输出结果追加一个换行符。
  • fmt::Debug:使用 {:?} 标记。格式化文本以供调试使用。
  • fmt::Display:使用 {} 标记。以更优雅和友好的风格来格式化文本。

一些常见格式的罗列

fn main() {
    println!("{}", 1); // 默认用法,打印Display
    println!("{:o}", 9); // 八进制
    println!("{:x}", 255); // 十六进制 小写
    println!("{:X}", 255); // 十六进制 大写
    println!("{:p}", &0); // 指针
    println!("{:b}", 15); // 二进制
    println!("{:e}", 10000f32); // 科学计数(小写)
    println!("{:E}", 10000f32); // 科学计数(大写)
    println!("{:?}", "test"); // 打印Debug
    println!("{:#?}", ("test1", "test2")); // 带换行和缩进的Debug打印
    println!("{a} {b} {b}", a = "x", b = "y"); // 命名参数


    assert_eq!(format!("Hello {:<5}!", "x"),  "Hello x    !"); // <右边界宽度
    assert_eq!(format!("Hello {:-<5}!", "x"), "Hello x----!"); // <右边界宽度+填充
    assert_eq!(format!("Hello {:^5}!", "x"),  "Hello   x  !"); // ^居中
    assert_eq!(format!("Hello {:>5}!", "x"),  "Hello     x!"); // >右边界宽度

    println!("Hello {:+}", 5); // +显示正号
    println!("{:#x}!", 27); // #显示十六进制
    assert_eq!(format!("Hello {:05}!", 5),  "Hello 00005!"); // 宽度
    println!("{:#09x}!", 27); // 十六进制数字宽度
    println!("Hello {0} is {1:.5}", "x", 0.01); // 小数位

    // $代入符号
    println!("Hello {0} is {1:.5}", "x", 0.01);
    // 0的位置是5,1的位置是x,2的位置是0.01
    println!("Hello {1} is {2:.0$}", 5, "x", 0.01);
    // 0的位置是x,1的位置是5,2的位置是0.01
    println!("Hello {0} is {2:.1$}", "x", 5, 0.01);
    // *统配代入符号
    // 0的位置是x,1的位置是5,2的位置是0.01
    println!("Hello {} is {:.*}",    "x", 5, 0.01);
    // 0的位置是5,1的位置是x,2的位置是0.01
    println!("Hello {1} is {2:.*}",  5, "x", 0.01);

    // 转义{}
    assert_eq!(format!("Hello {{}}"), "Hello {}");
    assert_eq!(format!("{{ Hello"), "{ Hello");
}

Debug trait

#[derive(Debug)] 所有类型都能推导fmt::Debug 的实现

// `derive` 属性会自动创建所需的实现,使这个 `struct` 能使用 `fmt::Debug` 打印。
#[derive(Debug)]
struct DebugPrintable(i32);

使用 {:#?} 美化打印

Display trait

fmt::Display 需要自己实现

// (使用 `use`)导入 `fmt` 模块使 `fmt::Display` 可用
use std::fmt;

// 定义一个结构体,咱们会为它实现 `fmt::Display`。以下是个简单的元组结构体
// `Structure`,包含一个 `i32` 元素。
struct Structure(i32);

// 为了使用 `{}` 标记,必须手动为类型实现 `fmt::Display` trait。
impl fmt::Display for Structure {
    // 这个 trait 要求 `fmt` 使用与下面的函数完全一致的函数签名
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // 仅将 self 的第一个元素写入到给定的输出流 `f`。返回 `fmt:Result`,此
        // 结果表明操作成功或失败。注意 `write!` 的用法和 `println!` 很相似。
        write!(f, "{}", self.0)
    }
}

如果想实现 {:b} 打印二进制则需要自己实现 std::fmt::Binary

Rust学习的关键资源

⭐️ Rust-Learning repo https://github.com/ctjhoa/rust-learning

官方教程(号称The Book)https://doc.rust-lang.org/stable/book/
中文版 https://kaisery.github.io/trpl-zh-cn/ https://rustwiki.org/zh-CN/book/

Easy Rust 用比较简单的英语写的教程,内容也较简单一些(入门)
https://dhghomon.github.io/easy_rust/Chapter_0.html

菜鸟Rust教程 https://www.runoob.com/rust/rust-tutorial.html

Rust By Example https://doc.rust-lang.org/rust-by-example/hello.html
⭐️ 中文版 https://rustwiki.org/zh-CN/rust-by-example/index.html

⭐️ Rust Cookbook https://rust-lang-nursery.github.io/rust-cookbook/

中文版:https://rustwiki.org/zh-CN/rust-cookbook

⭐️ Rust By Practice( Rust 练习实践 ) https://zh.practice.rs/why-exercise.html

⭐️ Rust圣经 https://course.rs/about-book.html

Rustc Book https://doc.rust-lang.org/rustc/index.html

Rust 🦀 and WebAssembly https://rustwasm.github.io/book/

The wasm-bindgen Guide https://rustwasm.github.io/wasm-bindgen/introduction.html

Rust参考手册:

英文 https://doc.rust-lang.org/reference/introduction.html

中文 https://rustwiki.org/zh-CN/reference/introduction.html

Rust 宏小册 https://zjp-cn.github.io/tlborm/

Rust 中的异步编程 https://huangjj27.github.io/async-book/index.html

Rust 秘典(死灵书) https://nomicon.purewhite.io/intro.html

官方API文档 https://doc.rust-lang.org/std/index.html

标准库中文版 https://rustwiki.org/zh-CN/std/

优质Rust项目 https://github.com/rust-unofficial/awesome-rust
中文版 https://github.com/PuzzledAlien/awesome-rust-cn

Rust 中文资源 https://github.com/rust-lang-cn

Effective Rust https://www.lurklurk.org/effective-rust/cover.html

⭐️ RustGYM https://rustgym.com/

Rust Atomics and Locks:Low-Level Concurrency in Practice https://marabos.nl/atomics/

The Tauri Documentation Work-in-progress https://jonaskruckenberg.github.io/tauri-docs-wip/introduction.html

Comprehensive Rust https://google.github.io/comprehensive-rust/
安卓团队编写的为期四天的 Rust 课程,从基本语法到高级主题,最后一天还会讲到 Rust 如何用于安卓开发。

视频:Rust Linz https://www.youtube.com/watch?v=OhCjnyBc448&list=PL85XCvVPmGQgL3lqQD5ivLNLfdAdxbE_u

视频:The Rust Lang Book https://www.youtube.com/watch?v=OX9HJsJUDxA&list=PLai5B987bZ9CoVR-QEIN9foz4QCJ0H2Y8&index=1

EPUB格式使用自定义字体

首先准备字体文件,放入 OEBPS/Fonts 文件夹

提供CSS,并使用字体

@font-face {
	font-family: '冬青黑体';
	src: url(../Fonts/DongQingHeiTi-1.otf);
}

@font-face {
	font-family: '汉仪隶书';
	src: url(../Fonts/hanyilishu.ttf);
}

META-INF/com.apple.ibooks.display-options.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<display_options>
	<platform name="*">
		<option name="specified-fonts">true</option>
	</platform>
</display_options>

这样在苹果系统的图书App里就可以使用“原字体”的设置方式。

注意:一定是这样

<option name="specified-fonts">true</option>

我因为用vscode保存时自动格式化,ibook是不认这样的变量的!浪费我一个多小时😷

<option name="specified-fonts">
  true
</option>