IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Rust tips #21 ~ #40

    smallnest发表于 2024-06-08 10:53:03
    love 0

    Tip #21

    在 Rust 中,我们经常使用 Clone() 或 Copy()。这两者之间的区别是什么?

    • Copy:支持 Copy 的类型可以安全地通过字节复制的方式进行复制,可以类比 C 语言中的 memcpy 函数。

    • Clone:支持 Clone 的类型也可以被复制,但它通常需要执行一些逻辑操作来完成深拷贝。

    Tip #22

    我之前忽略的一点是,Rust 中有 static 变量,可以用来追踪某些状态。当然, 可变的静态变量 (mutable static)是不支持的,但对于原始类型,可以考虑使用std::sync::atomic。这些可以被实例化为静态的,并且在后续可以被修改:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    static COUNTER: AtomicUsize = AtomicUsize::new(0);
    // Functions to get and increment *static* counter.
    fn increment_counter() {
    COUNTER.fetch_add(1, order: Ordering::Relaxed);
    }
    fn get_counter() -> usize {
    COUNTER.load(order: Ordering::Relaxed)
    }

    Tip #23

    对于大多数使用核心数据类型的结构体,你可以通过派生 Default trait自动生成一个基本的Default()实现:

    1
    2
    [derive(Default)]
    struct MyStruct { ... }

    Tip #24

    探索智能指针:

    • Box<T> 用于独占所有权,一旦它所在的作用域 {} 结束,它就会被释放。
    • Rc<T> 是一种引用计数的智能指针。只有当它的所有引用都不存在时,它才会被释放。
    • Arc<T> 是 Rc<T> 的线程安全版本。

    Tip #25

    在Rust中,trait的工作方式类似于其他语言中的接口定义。实现某个trait的结构体或枚举,在契约上必须提供trait中指定签名的函数 :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // Define a trait `Describable`
    trait Describable {
    fn describe(&self) -> String;
    }
    struct Person {
    name: String,
    age: u32,
    }
    // Implement the `Describable` trait for `Person`
    impl Describable for Person {
    fn describe(&self) -> String {
    format!("{} is {} years old.", self.name, self.age)
    }
    }

    Tip #26

    你知道Rust支持对大多数常见数据类型进行解构吗?这里有一个关于结构体的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    struct Rocket {
    name: String,
    num_engines: u32,
    }
    let falcon9: Rocket = Rocket{name: "Falcon 9".to_string(), num_engines: 9};
    // Destructure name and engines
    let Rocket {name: the_name: String, num_engines: the_engines: u32} = falcon9;
    println!("Rocket name {}, num engines {}", the_name, the_engines);

    这种解构方式允许你在一行中从结构体中提取多个字段,并给它们起新的名字或指定类型,这在处理复杂数据时非常有用。

    Tip #27

    Rust #区间表达式:

    • 包含区间(包含a到b,b也包括在内):a..=b
    • 半开区间(包含a到b-1):a..b
    • 从a开始:a..
    • 到b-1为止:..b
    • 到b为止(包括b):..=b
    • 完整区间:..

    Tip #28

    区间表达式(继续):
    区间表达式可以应用于for循环,或用于创建迭代器。别忘了调用collect()来实际执行迭代器:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let mut squares_a: Vec<u32> = vec![];
    for i: u32 in 1..=100 {
    squares_a.push(i * i);
    }
    // Map creates an iterator, but does not perform the computation.
    let squares_b_it: impl Iterator<Item = u32> = (1..=100).map(|x: u32| x * x);
    // Apply collect to "run" the iterator.
    let squares_b: Vec<u32> = (1..=100).map(|x: u32| x * x).collect();

    Tip #29

    迭代器可以通过 chain() 方法进行连续拼接。Rust 在处理可能含有或不含值的 Option 类型的连续操作时表现得尤为优雅。

    1
    2
    3
    4
    5
    6
    7
    let maybe_rocket = Some("Starship");
    let rockets = vec!["falcon1", "falcon2"];
    // Chain the two iterators together.
    for i in rockets.iter().chain(maybe_rocket) {
    println!("🚀 {}", i);
    }

    Tip #30

    如果需要以非可变方式将向量(vector)传递给函数,你可以使用 &[T](等同于 &Vec<T>)类型的参数,这也就是所谓的 切片(slice)。

    切片的优势包括:它们避免了所有权的转移,并且对于 #并发或 #并行操作是安全的。

    Tip #31

    动态调度(dynamic dispatch)简单来说,是在程序运行时动态地处理不同类型的特性,通过一个公共的特质(trait)来实现,从而使得(具有 Rust 特色的)多态成为可能。

    在 Rust 中,Box<dyn Trait> 通常表明使用了动态调度。

    Tip #32

    迭代器提供了一些非常方便的实用功能。其中之一是 all() 方法,它会检查迭代器中所有元素是否都满足给定的条件。

    这使我们能够以优雅且符合习惯用法的方式重写难看的基于for循环的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 难看的代码
    fn check_user_infos(user_infos: Vec<UserInfo>) -> bool {
    for u in user_infos.iter() {
    if !unique_tags.contains(&u.user_name.as_str()) {
    return false;
    }
    }
    true
    }
    // 优雅的代码
    fn check_user_infos(user_infos: Vec<UserInfo>) -> bool {
    user_infos.iter().all(|u| unique_tags.contains(&u.user_name.as_str()))
    }

    Tip #33

    let a: Arc<Mutex<Vec<f32>>> 这样的声明在视觉上是否让你觉得困扰?这时可以使用 type关键字来定义类型 别名(alias):

    1
    type SharedSafeVec<T> = Arc<Mutex<Vec<T>>>

    这样,你就可以使用 SharedSafeVec<f32> 来代替 Arc<Mutex<Vec<f32>>>。

    Tip #34

    Option<T>.map() 是一种将选项(Option)从一种类型转换为另一种类型的极佳方式。它能透明地处理 None 值的情况。

    请看以下示例,我们将 Option<u32> 转换为 Option<String>:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    fn main() {
    let number_option: Option<u32> = Some(42);
    let string_option: Option<String> = number_option.map(|num| num.to_string());
    println!("{:?}", string_option); // 输出: Some("42")
    let none_option: Option<u32> = None;
    let empty_string_option: Option<String> = none_option.map(|num| num.to_string());
    println!("{:?}", empty_string_option); // 输出: None
    }

    Tip #35

    什么是 trait bound? 当我们向带有泛型参数的函数中传递额外的trait名称,以便限制该泛型类型时,就是在谈论trait bound:

    1
    fn some_function<T: TraitA + TraitB>(param: T) { ... }

    你可以使用 "+" 运算符来组合多个特质。这样一来,类型 T 就需要同时满足 TraitA 和 TraitB 这两个特质的要求。

    Tip #36

    如需从应用程序获取更详细的日志输出,尝试导出环境变量 RUST_LOG={error, warn, info, debug, trace}。

    以下是一个使用 actix-web 运行的服务在 trace 模式下的示例,它会提供超级详尽的日志输出:
    ![[Pasted image 20240608183308.png]]

    Tip #37

    元组结构体对于封装值并附加可通过 Rust 的类型系统验证的元数据非常有用。

    元组结构体的一个妙用是模拟计量单位——这样就不会再混淆英尺和米了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    struct Feet(i32); // 定义一个元组结构体,表示英尺
    struct Meters(i32); // 定义另一个元组结构体,表示米
    impl From<Feet> for Meter {
    fn from(feet: Feet) -> Self {
    Meter(feet.0 * 0.3048) // 将英尺转换为米
    }
    }
    impl From<Meters> for Feet {
    fn from(meters: Meters) -> Self {
    Feet(meters.0 / 0.3048) // 将米转换为英尺
    }
    }
    fn is_longer_enough(meters: Meters) -> bool {
    meters.0 > 1
    }
    fn is_longer_enough(feet: Feet) -> bool {
    feet.0 > 3
    }

    Tip #38

    正在编写一个函数但还没准备好最终完成?可以使用 todo!() 或 unimplemented!() 宏来让代码保持可编译状态。但要记住,如果你的程序运行时遇到这些点,它将会panic!这对于开发阶段非常理想。🚧

    和第四条重复了

    Tip #39

    30秒速成指南:构建 Rust #模块

    创建你的模块结构:

    1
    2
    3
    4
    my_module/
    │ ├── mod.rs
    │ ├── component_1.rs
    │ └── component_2.rs

    在 mod.rs 中添加:

    1
    2
    mod component_1;
    mod component_2;

    或者新的方式:

    1
    2
    3
    4
    my_module/
    ├── component_1.rs
    └── component_2.rs
    my_module.rs

    🌟 小贴士:使用pub 关键字来定义公有访问权限。

    Tip #40

    实际上,Rust 中有两种类型的宏。声明式宏(declarative)和更高级的 过程式宏(procedural)。下面是一个使用 macro_rules! 宏来生成 println 功能的声明式宏示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    macro_rules! my_println {
    ($($arg:tt)*) => {
    println!($($arg)*);
    };
    }
    fn main() {
    my_println!("Hello, Rust!");
    }

    这段代码定义了一个名为 my_println! 的宏,它接收任意数量的参数并简单地将它们传递给标准库的 println! 宏,从而达到打印输出的目的。这是声明式宏的一个基本应用,它们基于规则匹配并在编译时展开。



沪ICP备19023445号-2号
友情链接