新Rust程序员需要学习的9个功能


Rust 是一种相当庞大且复杂的编程语言,具有许多特性。但我有一个好消息:不到20% 的功能会给你带来超过 80% 的结果。
以下是我认为在开始 Rust 时必须学习的功能。
 
枚举
Enums(也叫代数数据类型)当然是新Rustaceans最喜欢的功能,因为它们是Result和Option的基础。

enum Result<T, E> {
   Ok(T),
   Err(E),
}

pub enum Option<T> {
    None,
    Some(T),
}

枚举允许开发者安全地将其程序的所有可能状态编码到代码中,并在编译时检查他们是否忘记了某个情况。

#[derive(Debug, Clone, Copy)]
enum Platform {
    Linux,
    MacOS,
    Windows,
    Unknown,
}

impl fmt::Display for Platform {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Platform::Linux => write!(f, "Linux"),
            Platform::Macos => write!(f,
"macOS"),
           
// Compile time error! We forgot Windows and Unknown
        }
    }
}

 
异步等待(async-await)
线程被设计用来并行化计算密集型任务。然而,现在很多应用程序(如网络扫描器或网络服务器)都是I/O(输入/输出)密集型的,这意味着如果使用线程,我们的应用程序将花费大量时间来等待网络请求的完成,并使用超过必要的资源。

这些都是async-await所解决的问题,同时提供了一个很好的开发者体验。

  
Traits
你可能需要切换多个共享相同行为的类似类型的具体实现。

例如,一个存储驱动器。

struct FilesystemStorage {
  get() // ...
  put()
// ...
  delete()
// ...
}

struct S3Storage {
  get()
// ...
  put()
// ...
  delete()
// ...
}

为此,我们使用traits,在其他语言中也称为接口。

trait Storage {
  get() // ...
  put()
// ...
  delete()
// ...
}

impl Storage for FilesystemStorage {
 
// ...
}

impl Storage for S3Storage {
 
// ...
}


fn use_storage<S: Storage>(storage: S) {
 
// ...
}

 
智能指针
简而言之,它们允许开发者避免生命周期管理,从而写出更干净的代码。

它们也是traits obejcts的基础,允许你在运行时选择正确的实现(而不是用泛型编译时)。

struct MyService {
  db: Arc<DB>,
  mailer: Arc<dyn drivers::Mailer>,
  storage: Arc<dyn drivers::Storage>,
  other_service: Arc<other::Service>,
}

  
Collections
Rust的标准库的集合使用Rust编写复杂的算法和商业逻辑变得如此愉快。

let dedup_subdomains: HashSet<String> = subdomains.into_iter().collect();

 
迭代器
迭代器是一个使开发者能够遍历集合的对象。它们可以从标准库的大多数集合中获得。
fn filter() {
    let v = vec![-1, 2, -3, 4, 5].into_iter();

    let _positive_numbers: Vec<i32> = v.filter(|x: &i32| x.is_positive()).collect();
}

迭代器是懒惰的:如果它们不被消耗,它们就不会做任何事情。
 
组合器Combinators
组合器是一个非常有趣的话题。几乎所有你在互联网上找到的定义都会让你的脑袋爆炸,因为它们提出的问题比回答的多。
因此,这里是我的经验性定义。组合器是简化对某种类型T的操作的方法。它们有利于代码的函数性(方法链)风格。

let sum: u64 = vec![1, 2, 3].into_iter().map(|x| x * x).sum();

// Convert a `Result` to an `Option`
fn result_ok() {
    let _port: Option<String> = std::env::var(
"PORT").ok();
}

// Use a default `Result` if `Result` is `Err`
fn result_or() {
    let _port: Result<String, std::env::VarError> =
        std::env::var(
"PORT").or(Ok(String::from("8080")));
}

// Use a default value if empty, then apply a function
let http_port = std::env::var(
"PORT")
    .map_or(Ok(String::from(
"8080")), |env_val| env_val.parse::<u16>())?;

// Chain a function if `Result` is `Ok` or a different function if `Result` is `Err`
let master_key = std::env::var(
"MASTER_KEY")
    .map_err(|_| env_not_found(
"MASTER_KEY"))
    .map(base64::decode)??;

 
流Stream
流可以被粗略地定义为异步async世界的迭代器iterator。
当你想对同一类型的项目序列进行异步操作时,你应该使用它们,无论是网络套接字、文件,还是长生命的HTTP请求。

任何太大的东西都不能放在内存中,因此应该被分割成小块,或者可能稍后到达,但我们不知道什么时候,或者这只是一个集合(例如Vec或HashMap),我们需要对其应用异步操作。

它们还允许我们轻松地并发执行操作。

async fn compute_job(job: i64) -> i64 {
  // ...
}

#[tokio::main]
async fn main() {
    let jobs = 0..100;
    let concurrency = 42;

    stream::iter(jobs)
        .for_each_concurrent(concurrency, |job| compute_job(job)).await;
}

 
no_std
最后,Rust非常适用于嵌入式开发和shellcodes。因为这些环境不依赖于某个操作系统,所以你一般不能使用Rust的标准库,而是需要使用核心库。

对于这些用例,我们使用#![no_std]属性。

#![no_std]
#![no_main]

#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
    loop {}
}

#[no_mangle]
fn _start() {
  // ...
}