Rust 筆記 -- 錯(cuò)誤處理、泛型鹅龄、特質(zhì)揩慕、測(cè)試

The Rust Programming Language

Rust 編程語(yǔ)言筆記。

來源:The Rust Programming Language By Steve Klabnik, Carol Nichols

翻譯參考:Rust 語(yǔ)言術(shù)語(yǔ)中英文對(duì)照表

錯(cuò)誤處理

Rust 把錯(cuò)誤分為兩類:

  1. 可恢復(fù)的(recoverable):例如:文件未找到等砾层。該類錯(cuò)誤可以提示用戶查錯(cuò)后繼續(xù)運(yùn)行程序
  2. 不可恢復(fù)的(unrecoverable):例如:數(shù)組訪問越界等漩绵。該類錯(cuò)誤出現(xiàn)后必須終止程序

對(duì)于可恢復(fù)錯(cuò)誤贱案,Rust 采用 Result<T, E> 來處理肛炮;對(duì)于不可恢復(fù)錯(cuò)誤,Rust 采用 panic!() 宏(macro) 來處理宝踪。

在其他編程語(yǔ)言中侨糟,通常不會(huì)對(duì)錯(cuò)誤進(jìn)行分類,而是使用 Exception 統(tǒng)一處理瘩燥。

不可恢復(fù)錯(cuò)誤和 panic!

有兩種情況會(huì)執(zhí)行 panic!

  1. 顯式調(diào)用 panic!() 宏(macro)
  2. 程序出現(xiàn)錯(cuò)誤秕重,例如:數(shù)組訪問越界

默認(rèn)情況下,Rust 會(huì)打印錯(cuò)誤信息厉膀、解開(unwind)溶耘、清理?xiàng)?nèi)存、退出程序服鹅。通過環(huán)境變量凳兵,可以打印調(diào)用棧(calling stack),有助于更好 debug企软。

解開(unwind)棧內(nèi)存 VS 立即終止

默認(rèn)情況下庐扫,當(dāng) panic 發(fā)生時(shí),程序會(huì)開始解開(unwinding),Rust 會(huì)回到棧內(nèi)存中形庭,找到每個(gè)函數(shù)并清理數(shù)據(jù)铅辞。該操作需要花費(fèi)大量資源。另一種替代方式是萨醒,**立即終止(abort)**程序斟珊,清理內(nèi)存。

此時(shí)富纸,程序使用的內(nèi)存會(huì)由操作系統(tǒng)來清理倍宾。要切換到立即終止選項(xiàng),在 Cargo.toml 文件中的 [profile.release] 區(qū)域添加 panic = 'abort';

[profile.release]
panic = 'abort'

讓我們看一下顯式調(diào)用 panic!() 的情況:

fn main() {
    panic!("Crash and burn");
}

如果運(yùn)行上述程序胜嗓,編譯器會(huì)彈出:

$ ./test

thread 'main' panicked at 'Crash and burn', test.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

說明此時(shí)程序 panicked高职。

回溯

在 C 語(yǔ)言中,數(shù)組訪問越界是一種未定義的行為辞州。因此怔锌,如果索引不合法,C 會(huì)返回內(nèi)存中某處的數(shù)據(jù)变过,即使該處的內(nèi)存不屬于數(shù)組保存處的內(nèi)存埃元。這種行為稱為“緩沖區(qū)溢出(buffer overread)”,會(huì)導(dǎo)致很多安全問題媚狰。

在 Rust 中岛杀,數(shù)組訪問越界會(huì)導(dǎo)致錯(cuò)誤。

可以調(diào)用環(huán)境變量 RUST_BACKTRACE 來顯式調(diào)用棧的信息:

  • RUST_BACKTRACE=1: 打印簡(jiǎn)單信息
  • RUST_BACKTRACE=full:打印全部信息
$ RUST_BACKTRACE=1 ./test

thread 'main' panicked at 'Crash and burn', test.rs:2:5
stack backtrace:
   0: std::panicking::begin_panic
   1: test::main
   2: core::ops::function::FnOnce::call_once
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

Backtrace 就是一個(gè)包含所有函數(shù)的列表崭孤。Rust 對(duì)回溯的處理和其他語(yǔ)言一樣类嗤,從上往下讀,首先找到源文件行辨宠,代表問題/導(dǎo)致 panic 的函數(shù)遗锣,該行上面的所有行表示該行調(diào)用的函數(shù);該行下面的所有行代表被該行調(diào)用的函數(shù)嗤形。

可恢復(fù)錯(cuò)誤和 Result

Result

對(duì)于可恢復(fù)的錯(cuò)誤精偿,Rust 提供了 Result<T, E> 枚舉類型來處理這種錯(cuò)誤。

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

可以看到赋兵,Result 中的 TE 采用泛型(generic)定義笔咽,前者和 Ok 一起作為正常情況返回,后者和 Err 一起作為異常情況的返回霹期。

例如:

use std::fs::File;

fn main() {
    let greeting_file_result = File::open("hello.txt");

    let greeting_file = match greeting_file_result {
        Ok(file) => file,
        Err(error) => panic!("Problem opening the file: {:?}", error),
    };
}
  • 如果匹配 OkOk(File { fd: 3, path: "", read: true, write: false })
  • 如果匹配 ErrErr(Os { code: 2, kind: NotFound, message: "No such file or directory" })

處理錯(cuò)誤類型

如果要進(jìn)一步細(xì)化錯(cuò)誤的類型叶组,例如對(duì)于讀文件錯(cuò)誤,可以分為文件不存在或沒有權(quán)限訪問文件等经伙。那么通過嵌套 match 可以處理多種錯(cuò)誤的類型:

use std::fs::File;
use std::io::ErrorKind;

fn main() {
        let greeting_file_result = File::open("hello.txt");
    let greeting_file = match greeting_file_result {
            Ok(file) => file,
        Err(error) => match error.kind() {
                ErrorKind::NotFound => match File::create("hello.txt") {
                    Ok(fc) => fc,
                Err(e) => panic!("There is a problem when creating file: {:?}", e),
            },
            other_error => {
                    panic!("There is a problem when open the file: {:?}", other_error);     
            }
        },
      };
}

ErrorKind 也是一種枚舉類型扶叉,和 Result 以及 Option 不同勿锅,ErrorKind 需要使用 use 引入當(dāng)前的作用域。上面代碼中處理了NotFoundother_error 兩個(gè)枚舉值枣氧。

解包(unwrap) 和 expect

嵌套 match 的寫法有些冗余(verbose)溢十,因此,Rust 還提供了 unwrapexpect 方法來處理 panic 或者 Error达吞,這兩個(gè)函數(shù)都定義在 Result 上张弛。

use std::fs::File;

fn main() {
        let greeting_file_result = File::open("hello.txt").unwrap();
}
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" }', test.rs:4:60
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
use std::fs::File;

fn main() {
        let greeting_file_result = File::open("hello.txt")
                                                                    .expect("There is a problem when reading the file");
}
thread 'main' panicked at 'There is a problem when reading the file: Os { code: 2, kind: NotFound, message: "No such file or directory" }', test.rs:5:72
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

兩個(gè)方法的作用幾乎相同,在 production 代碼中酪劫,Rustaceans 傾向于使用 expect吞鸭,因?yàn)槠淇梢蕴峁└嗵崾拘畔ⅰ?/p>

傳播錯(cuò)誤和 ?運(yùn)算符

傳播錯(cuò)誤

當(dāng)被調(diào)用函數(shù)體中出現(xiàn)錯(cuò)誤時(shí)覆糟,與其在該函數(shù)中處理錯(cuò)誤刻剥,更常見的方式是把把錯(cuò)誤返回給調(diào)用函數(shù)以更好控制代碼的流程,這被稱為傳播錯(cuò)誤(propagating error)滩字。

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let username_file_result = File::open("hello.txt");

    let mut username_file = match username_file_result {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut username = String::new();

    match username_file.read_to_string(&mut username) {
        Ok(_) => Ok(username),
        Err(e) => Err(e),
    }
}

? 運(yùn)算符

上面的代碼有些冗長(zhǎng)造虏,可以使用 ? 運(yùn)算符縮短傳播錯(cuò)誤代碼:

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let mut username_file = File::open("hello.txt")?;
    let mut username = String::new();
    username_file.read_to_string(&mut username)?;
    Ok(username);
}

或者通過鏈?zhǔn)秸{(diào)用使上面的代碼更簡(jiǎn)潔:

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    let mut username = String::new();
    File::open("hello.txt")?.read_to_string(&mut username)?;
    Ok(username)
}

? 運(yùn)算符的作用和 match 幾乎相同,區(qū)別在于:? 運(yùn)算符包含了一個(gè)類型轉(zhuǎn)化的過程麦箍,把多種錯(cuò)誤返回值統(tǒng)一轉(zhuǎn)化同一種類型漓藕。該操作通過定義在 From trait 中的 from 函數(shù)來實(shí)現(xiàn),該函數(shù)把一種類型轉(zhuǎn)化為另一種類型挟裂。

具體來說享钞,? 運(yùn)算符把它所調(diào)用的返回錯(cuò)誤類型轉(zhuǎn)化為當(dāng)前函數(shù)定義的返回錯(cuò)誤類型。例如:當(dāng)前函數(shù)返回我們自定義的錯(cuò)誤類型 OurError诀蓉,而 ? 所作用的函數(shù)返回的是 io::Error栗竖,那么 ? 會(huì)調(diào)用 from 函數(shù)把 io::Error 轉(zhuǎn)化為 OurError

交排?的作用條件

使用 ? 運(yùn)算符時(shí)需要注意:該運(yùn)算符只能用于其作用值的類型和返回值類型兼容的函數(shù)划滋。這是因?yàn)?? 的作用在函數(shù)結(jié)束前提前返回值,類似于 match埃篓。

例如:match 作用的類型是 Result,而返回的錯(cuò)誤類型是 Err(e)根资。根據(jù) Result 的定義架专,這兩者是兼容的。

但是:

use std::fs::File;

fn main() {
    let greeting_file = File::open("hello.txt")?;
}

這種情況就會(huì)出現(xiàn)錯(cuò)誤玄帕。因?yàn)?main 函數(shù)的返回類型是 ()部脚,而 File::open 的返回類型是 Result

處理該錯(cuò)誤有兩種方法:

  1. 把函數(shù)的返回值類型改為 ? 作用值兼容的類型
  2. ? 替換為 match

main 函數(shù)的返回值

use std::error::Error;
use std::fs::File;

fn main() -> Result<(), Box<dyn Error>> {
    let greeting_file = File::open("hello.txt")?;

    Ok(())
}

與 C 語(yǔ)言的規(guī)范一致裤纹,在 Rust 中委刘,當(dāng) main 函數(shù)返回 Result<(), E> 時(shí):

  • 如果返回的是 OK(())丧没,那么 main 函數(shù)的返回值是 0
  • 如果返回的是 Err,那么 main 函數(shù)的返回值是 非零值锡移。

泛型呕童、特質(zhì)和生命周期

泛型

*泛型(generics)*用抽象類型來替代某種具體類型,大大減少了代碼的冗余淆珊。

函數(shù)夺饲、方法、結(jié)構(gòu)體施符、枚舉等都可以使用泛型往声。

定義泛型

使用泛型包括兩個(gè)步驟:

  1. 使用尖括號(hào)(angle brackets)<> 包裹標(biāo)識(shí)符 T<T>
  2. 在需要聲明類型處使用 T

一般來說,標(biāo)識(shí)符的名稱可以任意選定戳吝。但是在 Rust 中浩销,為了簡(jiǎn)單,通常使用簡(jiǎn)短且大寫字母 T听哭,表示 Type撼嗓。

用于函數(shù)定義

泛型可以用于函數(shù)定義:

fn largest<T>(list: &<T>) -> &T {
        let mut largest = &list[0];
  
    for item in list {
        if item > largest {
                largest = item;  
        }
    }
    larest
}

使用泛型時(shí)要注意潛在的錯(cuò)誤。例如:上面的函數(shù)找到列表中的最大值欢唾。但是編譯器會(huì)在 item > largest 行報(bào)錯(cuò)且警,這是因?yàn)閮烧叨际?&<T> 類型,但不是所有的類型都可以比較礁遣。

用于結(jié)構(gòu)體

泛型可以用于結(jié)構(gòu)體定義:

struct Point<T> {
    x: T,
    y: T,
}

struct Point<T, U> {
        x: T,
    y: U,
}

第一個(gè) Point 只使用了個(gè)類型斑芜,所以字段 xy 必須是同種類型祟霍。

第二個(gè) Point 使得 xy 的類型既可以相同也可以不同杏头。

用于枚舉

泛型可以用于枚舉定義,例如:Option 和 Result:

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

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

用于方法

泛型可以用于方法定義:

struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
            &self.x 
    }   
}

注意:在方法上使用泛型時(shí)沸呐,需要在 impl 關(guān)鍵字后添加 <T>醇王,這是為了告訴 Rust 該方法使用了泛型。

也可以僅給某些類型添加方法:

impl Point<f32> {
    fn distance_from_origin(&self) -> f32 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

上面的代碼表示崭添,只有類型為 f32Point 結(jié)構(gòu)體才有 distance_from_origin 方法寓娩。

性能

與使用具體類型相比,使用泛型不會(huì)導(dǎo)致性能變差呼渣。

Rust 使用單態(tài)(Monomorphization) 完成這一點(diǎn)棘伴。單態(tài)在編譯時(shí)把所有的泛型轉(zhuǎn)化為具體類型。

特質(zhì)

*特質(zhì)(traits)*定義了某種特定類型的功能屁置,并且可以和其他類型共享焊夸。

*特質(zhì)約束(trait bound)*定義泛型能夠具有某種特定行為。

特質(zhì)類似于其他編程語(yǔ)言的 接口(interface)蓝角,但是也有著一些區(qū)別阱穗。

定義特質(zhì)

使用關(guān)鍵字 trait 定義特質(zhì):

pub trait Summary {
    fn summarize(&self) -> String;
}

trait 塊由函數(shù)簽名組成饭冬。

實(shí)現(xiàn)特質(zhì)

類似于方法,實(shí)現(xiàn)特質(zhì)同樣使用 impl 關(guān)鍵字揪阶,此外還要使用 for 關(guān)鍵字指明要實(shí)現(xiàn)的對(duì)象昌抠。

pub struct Tweet {
    pub author: String,
    pub content: String,
    pub length: u32,
}

impl Summary for Tweet {
        fn summarize(&self) -> String {
            format!("{} {}", self.author, self.content);  
    }
}

/*
// 給另一結(jié)構(gòu)體實(shí)現(xiàn)同名 trait
impl Summary for Article {
        --snip--
}
*/

fn main() {
    let tweet = Tweet {
            author: String::from("Mitchell"),
        content: String::from("Implementing a trait"),
      };
    tweet.summarize();
}

注意:當(dāng)且僅當(dāng)其中一個(gè) trait 或者實(shí)現(xiàn) trait 的類型位于當(dāng)前 crate 的作用域時(shí),才可以在其他的 trait 中引用同名 trait遣钳,并給出不同的實(shí)現(xiàn)扰魂。該限制是 coherence 特性的一部分,也被稱為孤兒原則(orphan rule)蕴茴。


測(cè)試

Rust 中的測(cè)試函數(shù)用于測(cè)試被測(cè)試代碼是否按照預(yù)期運(yùn)行劝评。

測(cè)試函數(shù)體通常包含三部分:

  1. 設(shè)置所需的變量或者狀態(tài)
  2. 運(yùn)行代碼并測(cè)試
  3. 判斷是否為預(yù)期結(jié)果

測(cè)試函數(shù)

Rust 中的測(cè)試函數(shù)使用 test屬性。屬性是關(guān)于 Rust 代碼的元數(shù)據(jù)(metadata)倦淀。例如:derive 就是一種元數(shù)據(jù)蒋畜。

為了把某函數(shù)變?yōu)闇y(cè)試函數(shù),需要在函數(shù)簽名行之上添加 #[test]撞叽。使用 cargo test運(yùn)行測(cè)試姻成。

一般在創(chuàng)建新項(xiàng)目時(shí),Rust 會(huì)自動(dòng)添加含有測(cè)試函數(shù)的測(cè)試模塊愿棋,測(cè)試模塊包含了測(cè)試代碼的模版科展。

例如:

#[cfg(test)]
mod tests {
        #[test]
    fn it_works() {
            let result = 2 + 2;
        assert_eq!(result, 4);
    }
}

assert! 宏

assert! 宏由標(biāo)準(zhǔn)庫(kù)提供,它用于評(píng)估布爾值糠雨。如果評(píng)估結(jié)果為 true才睹,程序正常運(yùn)行;否則甘邀,assert! 宏調(diào)用 panic! 宏導(dǎo)致測(cè)試失敗琅攘。

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn larger_can_hold_smaller() {
        let larger = Rectangle {
            width: 8,
            height: 7,
        };
        let smaller = Rectangle {
            width: 5,
            height: 1,
        };

        assert!(larger.can_hold(&smaller));
    }
}

assert_eq!, assert_ne!

assert_eq!, assert_ne! 分別測(cè)試參數(shù)相等或者不等。

pub fn add_two(a: i32) -> i32 {
    a + 2
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_adds_two() {
        assert_eq!(4, add_two(2));
    }
}

在 Rust 中松邪,assert_eq! 的參數(shù)為 leftright坞琴,如果兩者相等,那么測(cè)試通過逗抑;否則測(cè)試失敗剧辐。assert_ne! 則正好相反。

參數(shù) leftright 表明參數(shù)的順序不重要锋八。而在其他編程語(yǔ)言中浙于,測(cè)試相等性的函數(shù)通常有著嚴(yán)格的順序,例如:參數(shù)分別為 expectactual挟纱,那么第一個(gè)參數(shù)只能是預(yù)期值,第二個(gè)參數(shù)是測(cè)試值腐宋。

在底層實(shí)現(xiàn)中紊服,assert_eq!, assert_ne! 分別使用了 ==!= 運(yùn)算符檀轨。

添加個(gè)性化錯(cuò)誤信息

可以在 assert!, assert_eq!, assert_ne! 中把個(gè)性化錯(cuò)誤信息作為可選參數(shù)傳入,使得用戶交互更加友好欺嗤。

#[test]
    fn greeting_contains_name() {
        let result = greeting("Carol");
        assert!(
            result.contains("Carol"),
            "Greeting did not contain name, value was `{}`",
            result
        );
    }

should_panic

should_panic 是一種屬性参萄,用于測(cè)試函數(shù)體中的內(nèi)容是否 panic,如果 panic 則測(cè)試通過煎饼。

pub struct Guess {
    value: i32,
}

impl Guess {
    pub fn new(value: i32) -> Guess {
        if value < 1 || value > 100 {
            panic!("Guess value must be between 1 and 100, got {}.", value);
        }

        Guess { value }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[should_panic]
    fn greater_than_100() {
        Guess::new(200);
    }
}

Result<T, E>

使用 Result<T, E> 來編寫測(cè)試:

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() -> Result<(), String> {
        if 2 + 2 == 4 {
            Ok(())
        } else {
            Err(String::from("two plus two does not equal four"))
        }
    }
}

在使用了 Result<T, E> 的測(cè)試中不能使用 #[should_panic]讹挎。

控制測(cè)試的運(yùn)行

通過給 cargo test 添加參數(shù)來控制測(cè)試的運(yùn)行。

并行還是連續(xù)

在默認(rèn)情況下吆玖,Rust 的測(cè)試是并行(parallel)的筒溃,這意味著測(cè)試的速度會(huì)更快,但是需要測(cè)試之間互不依賴沾乘。

如果要改為連續(xù)執(zhí)行怜奖,通過添加 ``--test-threads=1flag 來表示希望使用1` 個(gè)線程來運(yùn)行測(cè)試:

cargo test -- --test-threads=1

顯示輸出結(jié)果

在默認(rèn)情況下,Rust 只會(huì)顯示測(cè)試失敗的用例翅阵。

通過添加 --show-output flag 來額外顯示測(cè)試通過的用例:

cargo test -- --show-output

按名稱運(yùn)行子測(cè)試

有些時(shí)候只需要運(yùn)行部分測(cè)試歪玲,可以通過具體指定測(cè)試的名稱來部分執(zhí)行測(cè)試,假設(shè)有三個(gè)測(cè)試:

#[cfg(test)]
mod tests {
    use super::*;
  
    #[test]
    fn add_one() { /* --snip */ };
    #[test]
    fn add_two() { /* --snip */ };
      #[test]
    fn add_three() { /* --snip */ };
}
  • cargo test 命令運(yùn)行全部測(cè)試
  • carge test add_one 只運(yùn)行 add_one() 測(cè)試函數(shù)
  • cargo test add 運(yùn)行所有名稱包含 add 的函數(shù)掷匠,在本例中滥崩,運(yùn)行全部函數(shù)

忽略某些測(cè)試

通過添加 #[ignore] 屬性來忽略某些測(cè)試。

運(yùn)行 cargo test 命令后讹语,被忽略的測(cè)試函數(shù)不會(huì)進(jìn)行測(cè)試钙皮。

#[test]
#[ignore]
fn ingored_test() { /* --snip-- */ }

組織測(cè)試代碼

Rust 中主要有兩種測(cè)試方式:

  1. 單元測(cè)試(unit test):一次獨(dú)立測(cè)試一個(gè)模塊
  2. 集成測(cè)試(integration test):作為外部庫(kù)測(cè)試代碼

單元測(cè)試

單元測(cè)試的慣例是:在每個(gè)文件中創(chuàng)建 tests 模塊,該模塊包含所有測(cè)試函數(shù)募强,以 cfg(test) 標(biāo)識(shí)株灸。

標(biāo)識(shí)符 #[cfg(test)] 表示只有當(dāng) cargo test 時(shí)才運(yùn)行測(cè)試,在 cargo build 時(shí)并不運(yùn)行測(cè)試擎值。這樣的設(shè)計(jì)可以節(jié)約編譯時(shí)間慌烧。cfg 的意思是 configuration

私有函數(shù)

單元測(cè)試可以測(cè)試私有函數(shù)鸠儿。

pub fn add_two(a: i32) -> i32 {
    internal_adder(a, 2)
}

fn internal_adder(a: i32, b: i32) -> i32 {
    a + b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn internal() {
        assert_eq!(4, internal_adder(2, 2));
    }
}

集成測(cè)試

對(duì)于本地代碼來說屹蚊,集成測(cè)試作為外部庫(kù)的形式,因此只能用于測(cè)試公有函數(shù)进每。

可以這樣組織集成測(cè)試的目錄:

├── Cargo.lock
├── Cargo.toml
├── src
│   └── lib.rs
└── tests
    ├── common
    │   └── mod.rs
    └── integration_test.rs

這是舊規(guī)范的命名方式汹粤,這樣命名使得 Rust 得知 common 中的 mod.rs 并不作為集成測(cè)試的一部分。

我們可以把要測(cè)試的函數(shù)都寫在 common/mod.rs 中田晚,在 integration_test.rs 中開展具體測(cè)試嘱兼。

集成測(cè)試只針對(duì)庫(kù) crate,如果代碼中只包含二進(jìn)制 crate贤徒,那么不能使用集成測(cè)試芹壕。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末汇四,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子踢涌,更是在濱河造成了極大的恐慌通孽,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件睁壁,死亡現(xiàn)場(chǎng)離奇詭異背苦,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)潘明,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門行剂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人钉疫,你說我怎么就攤上這事硼讽。” “怎么了牲阁?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵固阁,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我城菊,道長(zhǎng)备燃,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任凌唬,我火速辦了婚禮并齐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘客税。我一直安慰自己况褪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布更耻。 她就那樣靜靜地躺著测垛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪秧均。 梳的紋絲不亂的頭發(fā)上食侮,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音目胡,去河邊找鬼锯七。 笑死,一個(gè)胖子當(dāng)著我的面吹牛誉己,可吹牛的內(nèi)容都是我干的眉尸。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼效五!你這毒婦竟也來了地消?” 一聲冷哼從身側(cè)響起炉峰,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤畏妖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后疼阔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體戒劫,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年婆廊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了迅细。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡淘邻,死狀恐怖茵典,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情宾舅,我是刑警寧澤统阿,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站筹我,受9級(jí)特大地震影響扶平,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蔬蕊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一结澄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧岸夯,春花似錦麻献、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至破镰,卻和暖如春餐曼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鲜漩。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工源譬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人孕似。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓踩娘,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子养渴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容