mirror of https://github.com/sunface/rust-course
commit
beaa7bebc1
@ -0,0 +1,11 @@
|
|||||||
|
Rust相关英文书籍
|
||||||
|
|
||||||
|
1. [Rust语言基础教学](https://doc.rust-lang.org/book/)
|
||||||
|
|
||||||
|
2. [Rust语言示例](https://doc.rust-lang.org/stable/rust-by-example/index.html)
|
||||||
|
|
||||||
|
3. [Cargo教学](https://doc.rust-lang.org/cargo/index.html)
|
||||||
|
|
||||||
|
4. [Rust性能之书](https://nnethercote.github.io/perf-book/title-page.html)
|
||||||
|
|
||||||
|
5. [Rust异步编程](https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html)
|
@ -0,0 +1,2 @@
|
|||||||
|
## Pin、UnPin
|
||||||
|
1. https://blog.cloudflare.com/pin-and-unpin-in-rust/
|
@ -0,0 +1,13 @@
|
|||||||
|
## #[derive(Default)]
|
||||||
|
```rust
|
||||||
|
#[derive(Default)]
|
||||||
|
struct NotSend(Rc<()>);
|
||||||
|
|
||||||
|
fn require_send(_: impl Send) {}
|
||||||
|
|
||||||
|
async fn bar() {}
|
||||||
|
async fn foo() {
|
||||||
|
//Returns the "default value" for a type.
|
||||||
|
NotSend::default();
|
||||||
|
}
|
||||||
|
```
|
@ -0,0 +1,150 @@
|
|||||||
|
## 读取全部内容
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Create a path to the desired file
|
||||||
|
let path = Path::new("hello.txt");
|
||||||
|
let display = path.display();
|
||||||
|
|
||||||
|
// Open the path in read-only mode, returns `io::Result<File>`
|
||||||
|
let mut file = match File::open(&path) {
|
||||||
|
Err(why) => panic!("couldn't open {}: {}", display, why),
|
||||||
|
Ok(file) => file,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Read the file contents into a string, returns `io::Result<usize>`
|
||||||
|
let mut s = String::new();
|
||||||
|
match file.read_to_string(&mut s) {
|
||||||
|
Err(why) => panic!("couldn't read {}: {}", display, why),
|
||||||
|
Ok(_) => print!("{} contains:\n{}", display, s),
|
||||||
|
}
|
||||||
|
|
||||||
|
// `file` goes out of scope, and the "hello.txt" file gets closed
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 读取全部行
|
||||||
|
This process is more efficient than creating a String in memory especially working with larger files.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{self, BufRead};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// File hosts must exist in current path before this produces output
|
||||||
|
if let Ok(lines) = read_lines("./hosts") {
|
||||||
|
// Consumes the iterator, returns an (Optional) String
|
||||||
|
for line in lines {
|
||||||
|
if let Ok(ip) = line {
|
||||||
|
println!("{}", ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The output is wrapped in a Result to allow matching on errors
|
||||||
|
// Returns an Iterator to the Reader of the lines of the file.
|
||||||
|
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
|
||||||
|
where P: AsRef<Path>, {
|
||||||
|
let file = File::open(filename)?;
|
||||||
|
Ok(io::BufReader::new(file).lines())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 一些常用的文件操作
|
||||||
|
```rust
|
||||||
|
use std::fs;
|
||||||
|
use std::fs::{File, OpenOptions};
|
||||||
|
use std::io;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::os::unix;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
fn cat(path: &Path) -> io::Result<String> {
|
||||||
|
let mut f = File::open(path)?;
|
||||||
|
let mut s = String::new();
|
||||||
|
f.read_to_string(&mut s)?;
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A simple implementation of `% echo s > path`
|
||||||
|
fn echo(s: &str, path: &Path) -> io::Result<()> {
|
||||||
|
let mut f = File::create(path)?;
|
||||||
|
|
||||||
|
f.write_all(s.as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// A simple implementation of `% touch path` (ignores existing files)
|
||||||
|
fn touch(path: &Path) -> io::Result<()> {
|
||||||
|
match OpenOptions::new().create(true).write(true).open(path) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("`mkdir a`");
|
||||||
|
// Create a directory, returns `io::Result<()>`
|
||||||
|
match fs::create_dir("a") {
|
||||||
|
Err(why) => println!("! {:?}", why.kind()),
|
||||||
|
Ok(_) => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("`echo hello > a/b.txt`");
|
||||||
|
// The previous match can be simplified using the `unwrap_or_else` method
|
||||||
|
echo("hello", &Path::new("a/b.txt")).unwrap_or_else(|why| {
|
||||||
|
println!("! {:?}", why.kind());
|
||||||
|
});
|
||||||
|
|
||||||
|
println!("`mkdir -p a/c/d`");
|
||||||
|
// Recursively create a directory, returns `io::Result<()>`
|
||||||
|
fs::create_dir_all("a/c/d").unwrap_or_else(|why| {
|
||||||
|
println!("! {:?}", why.kind());
|
||||||
|
});
|
||||||
|
|
||||||
|
println!("`touch a/c/e.txt`");
|
||||||
|
touch(&Path::new("a/c/e.txt")).unwrap_or_else(|why| {
|
||||||
|
println!("! {:?}", why.kind());
|
||||||
|
});
|
||||||
|
|
||||||
|
println!("`ln -s ../b.txt a/c/b.txt`");
|
||||||
|
// Create a symbolic link, returns `io::Result<()>`
|
||||||
|
if cfg!(target_family = "unix") {
|
||||||
|
unix::fs::symlink("../b.txt", "a/c/b.txt").unwrap_or_else(|why| {
|
||||||
|
println!("! {:?}", why.kind());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("`cat a/c/b.txt`");
|
||||||
|
match cat(&Path::new("a/c/b.txt")) {
|
||||||
|
Err(why) => println!("! {:?}", why.kind()),
|
||||||
|
Ok(s) => println!("> {}", s),
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("`ls a`");
|
||||||
|
// Read the contents of a directory, returns `io::Result<Vec<Path>>`
|
||||||
|
match fs::read_dir("a") {
|
||||||
|
Err(why) => println!("! {:?}", why.kind()),
|
||||||
|
Ok(paths) => for path in paths {
|
||||||
|
println!("> {:?}", path.unwrap().path());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("`rm a/c/e.txt`");
|
||||||
|
// Remove a file, returns `io::Result<()>`
|
||||||
|
fs::remove_file("a/c/e.txt").unwrap_or_else(|why| {
|
||||||
|
println!("! {:?}", why.kind());
|
||||||
|
});
|
||||||
|
|
||||||
|
println!("`rmdir a/c/d`");
|
||||||
|
// Remove an empty directory, returns `io::Result<()>`
|
||||||
|
fs::remove_dir("a/c/d").unwrap_or_else(|why| {
|
||||||
|
println!("! {:?}", why.kind());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
@ -0,0 +1,6 @@
|
|||||||
|
## Key of hashmap
|
||||||
|
Any type that implements the Eq and Hash traits can be a key in HashMap.
|
||||||
|
|
||||||
|
Note that f32 and f64 do not implement Hash, likely because floating-point precision errors would make using them as hashmap keys horribly error-prone.
|
||||||
|
|
||||||
|
All collection classes implement Eq and Hash if their contained type also respectively implements Eq and Hash. For example, Vec<T> will implement Hash if T implements Hash.
|
@ -0,0 +1,2 @@
|
|||||||
|
## 容器类型说明图
|
||||||
|
https://docs.google.com/presentation/d/1q-c7UAyrUlM-eZyTo1pd8SZ0qwA_wYxmPZVOQkoDmH4/edit#slide=id.p
|
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
## Reading input as Raw Bytes
|
||||||
|
The built-in String type uses UTF-8 internally, which adds a small, but nonzero overhead caused by UTF-8 validation when you read input into it. If you just want to process input bytes without worrying about UTF-8 (for example if you handle ASCII text), you can use `BufRead::read_until`.
|
||||||
|
|
@ -0,0 +1,38 @@
|
|||||||
|
一些lifetime消除规则
|
||||||
|
|
||||||
|
|
||||||
|
### 1
|
||||||
|
Let's talk about a feature that's available in both editions: we've added some additional elision rules for `impl` blocks and function definitions. Code like this:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl<'a> Reader for BufReader<'a> {
|
||||||
|
// methods go here
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
can now be written like this:
|
||||||
|
```rust
|
||||||
|
impl Reader for BufReader<'_> {
|
||||||
|
// methods go here
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `'_` lifetime still shows that `BufReader` takes a parameter, but we don't need to create a name for it anymore.
|
||||||
|
|
||||||
|
### 2
|
||||||
|
Lifetimes are still required to be defined in structs. However, we no longer require as much boilerplate as before:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Rust 2015
|
||||||
|
struct Ref<'a, T: 'a> {
|
||||||
|
field: &'a T
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rust 2018
|
||||||
|
struct Ref<'a, T> {
|
||||||
|
field: &'a T
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `: 'a` is inferred. You can still be explicit if you prefer. We're considering some more options for elision here in the future, but have no concrete plans yet.
|
||||||
|
|
@ -0,0 +1,31 @@
|
|||||||
|
## layout
|
||||||
|
```go
|
||||||
|
.
|
||||||
|
├── Cargo.lock
|
||||||
|
├── Cargo.toml
|
||||||
|
├── src/
|
||||||
|
│ ├── lib.rs
|
||||||
|
│ ├── main.rs
|
||||||
|
│ └── bin/
|
||||||
|
│ ├── named-executable.rs
|
||||||
|
│ ├── another-executable.rs
|
||||||
|
│ └── multi-file-executable/
|
||||||
|
│ ├── main.rs
|
||||||
|
│ └── some_module.rs
|
||||||
|
├── benches/
|
||||||
|
│ ├── large-input.rs
|
||||||
|
│ └── multi-file-bench/
|
||||||
|
│ ├── main.rs
|
||||||
|
│ └── bench_module.rs
|
||||||
|
├── examples/
|
||||||
|
│ ├── simple.rs
|
||||||
|
│ └── multi-file-example/
|
||||||
|
│ ├── main.rs
|
||||||
|
│ └── ex_module.rs
|
||||||
|
└── tests/
|
||||||
|
├── some-integration-tests.rs
|
||||||
|
└── multi-file-test/
|
||||||
|
├── main.rs
|
||||||
|
└── test_module.rs
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,7 @@
|
|||||||
|
## books
|
||||||
|
1. [Rust性能之书](https://nnethercote.github.io/perf-book/title-page.html)
|
||||||
|
2. [How to write fast rust code](https://likebike.com/posts/How_To_Write_Fast_Rust_Code.html#emit-asm)
|
||||||
|
|
||||||
|
|
||||||
|
## crates
|
||||||
|
1. [高性能Mutex库](https://github.com/Amanieu/parking_lot)
|
@ -0,0 +1,7 @@
|
|||||||
|
一些关于println的技巧
|
||||||
|
|
||||||
|
## 打印对象地址
|
||||||
|
```rust
|
||||||
|
let v= vec![1,2,3];
|
||||||
|
println!("{:p}",v.as_ptr())
|
||||||
|
```
|
@ -0,0 +1,34 @@
|
|||||||
|
## struct中的一个字段是另外一个字段的指针
|
||||||
|
```rust
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Test {
|
||||||
|
a: String,
|
||||||
|
b: *const String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Test {
|
||||||
|
fn new(txt: &str) -> Self {
|
||||||
|
Test {
|
||||||
|
a: String::from(txt),
|
||||||
|
b: std::ptr::null(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&mut self) {
|
||||||
|
let self_ref: *const String = &self.a;
|
||||||
|
self.b = self_ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn a(&self) -> &str {
|
||||||
|
&self.a
|
||||||
|
}
|
||||||
|
|
||||||
|
fn b(&self) -> &String {
|
||||||
|
assert!(!self.b.is_null(), "Test::b called without Test::init being called first");
|
||||||
|
unsafe { &*(self.b) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Test provides methods to get a reference to the value of the fields a and b. Since b is a reference to a we store it as a pointer since the borrowing rules of Rust doesn't allow us to define this lifetime. We now have what we call a self-referential struct.
|
||||||
|
|
@ -0,0 +1,7 @@
|
|||||||
|
## code snippets
|
||||||
|
1. https://stackoverflow.com/questions/67823680/open-a-single-file-from-a-zip-archive-and-pass-on-as-read-instance/67828823#67828823
|
||||||
|
|
||||||
|
|
||||||
|
## crates
|
||||||
|
1. https://github.com/Kimundi/owning-ref-rs
|
||||||
|
2. https://github.com/joshua-maros/ouroboros
|
@ -0,0 +1,82 @@
|
|||||||
|
## 字符串常量
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
// You can use escapes to write bytes by their hexadecimal values...
|
||||||
|
let byte_escape = "I'm writing \x52\x75\x73\x74!";
|
||||||
|
println!("What are you doing\x3F (\\x3F means ?) {}", byte_escape);
|
||||||
|
|
||||||
|
// ...or Unicode code points.
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() {
|
||||||
|
let raw_str = r"Escapes don't work here: \x3F \u{211D}";
|
||||||
|
println!("{}", raw_str);
|
||||||
|
|
||||||
|
// If you need quotes in a raw string, add a pair of #s
|
||||||
|
let quotes = r#"And then I said: "There is no escape!""#;
|
||||||
|
println!("{}", quotes);
|
||||||
|
|
||||||
|
// If you need "# in your string, just use more #s in the delimiter.
|
||||||
|
// There is no limit for the number of #s you can use.
|
||||||
|
let longer_delimiter = r###"A string with "# in it. And even "##!"###;
|
||||||
|
println!("{}", longer_delimiter);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bytes string
|
||||||
|
Want a string that's not UTF-8? (Remember, `str` and `String` must be valid UTF-8). Or maybe you want an array of bytes that's mostly text? Byte strings to the rescue!
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Note that this is not actually a `&str`
|
||||||
|
let bytestring: &[u8; 21] = b"this is a byte string";
|
||||||
|
|
||||||
|
// Byte arrays don't have the `Display` trait, so printing them is a bit limited
|
||||||
|
println!("A byte string: {:?}", bytestring);
|
||||||
|
|
||||||
|
// Byte strings can have byte escapes...
|
||||||
|
let escaped = b"\x52\x75\x73\x74 as bytes";
|
||||||
|
// ...but no unicode escapes
|
||||||
|
// let escaped = b"\u{211D} is not allowed";
|
||||||
|
println!("Some escaped bytes: {:?}", escaped);
|
||||||
|
|
||||||
|
|
||||||
|
// Raw byte strings work just like raw strings
|
||||||
|
let raw_bytestring = br"\u{211D} is not escaped here";
|
||||||
|
println!("{:?}", raw_bytestring);
|
||||||
|
|
||||||
|
// Converting a byte array to `str` can fail
|
||||||
|
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"#;
|
||||||
|
|
||||||
|
// Byte strings don't have to be UTF-8
|
||||||
|
let shift_jis = b"\x82\xe6\x82\xa8\x82\xb1\x82\xbb"; // "ようこそ" in SHIFT-JIS
|
||||||
|
|
||||||
|
// But then they can't always be converted to `str`
|
||||||
|
match str::from_utf8(shift_jis) {
|
||||||
|
Ok(my_str) => println!("Conversion successful: '{}'", my_str),
|
||||||
|
Err(e) => println!("Conversion failed: {:?}", e),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
@ -0,0 +1,71 @@
|
|||||||
|
关于调用os的命令
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let output = Command::new("rustc")
|
||||||
|
.arg("--versn")
|
||||||
|
.output().unwrap_or_else(|e| {
|
||||||
|
panic!("failed to execute process: {}", e)
|
||||||
|
});
|
||||||
|
|
||||||
|
if output.status.success() {
|
||||||
|
let s = String::from_utf8_lossy(&output.stdout);
|
||||||
|
|
||||||
|
print!("rustc succeeded and stdout was:\n{}", s);
|
||||||
|
} else {
|
||||||
|
let s = String::from_utf8_lossy(&output.stderr);
|
||||||
|
|
||||||
|
print!("rustc failed and stderr was:\n{}", s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pipes
|
||||||
|
The std::Child struct represents a running child process, and exposes the stdin, stdout and stderr handles for interaction with the underlying process via pipes.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
static PANGRAM: &'static str =
|
||||||
|
"the quick brown fox jumped over the lazy dog\n";
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Spawn the `wc` command
|
||||||
|
let process = match Command::new("wc")
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.spawn() {
|
||||||
|
Err(why) => panic!("couldn't spawn wc: {}", why),
|
||||||
|
Ok(process) => process,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Write a string to the `stdin` of `wc`.
|
||||||
|
//
|
||||||
|
// `stdin` has type `Option<ChildStdin>`, but since we know this instance
|
||||||
|
// must have one, we can directly `unwrap` it.
|
||||||
|
match process.stdin.unwrap().write_all(PANGRAM.as_bytes()) {
|
||||||
|
Err(why) => panic!("couldn't write to wc stdin: {}", why),
|
||||||
|
Ok(_) => println!("sent pangram to wc"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because `stdin` does not live after the above calls, it is `drop`ed,
|
||||||
|
// and the pipe is closed.
|
||||||
|
//
|
||||||
|
// This is very important, otherwise `wc` wouldn't start processing the
|
||||||
|
// input we just sent.
|
||||||
|
|
||||||
|
// The `stdout` field also has type `Option<ChildStdout>` so must be unwrapped.
|
||||||
|
let mut s = String::new();
|
||||||
|
match process.stdout.unwrap().read_to_string(&mut s) {
|
||||||
|
Err(why) => panic!("couldn't read wc stdout: {}", why),
|
||||||
|
Ok(_) => print!("wc responded with:\n{}", s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
|||||||
|
Cargo looks for integration tests in `tests` directory next to `src`.
|
||||||
|
|
||||||
|
File `src/lib.rs`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Define this in a crate called `adder`.
|
||||||
|
pub fn add(a: i32, b: i32) -> i32 {
|
||||||
|
a + b
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
File with `test: tests/integration_test.rs`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[test]
|
||||||
|
fn test_add() {
|
||||||
|
assert_eq!(adder::add(3, 2), 5);
|
||||||
|
}
|
||||||
|
```
|
@ -0,0 +1,34 @@
|
|||||||
|
## Development dependencies
|
||||||
|
Sometimes there is a need to have dependencies for tests (or examples, or benchmarks) only. Such dependencies are added to Cargo.toml in the [dev-dependencies] section. These dependencies are not propagated to other packages which depend on this package.
|
||||||
|
|
||||||
|
One such example is using a crate that extends standard assert! macros.
|
||||||
|
File Cargo.toml:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# standard crate data is left out
|
||||||
|
[dev-dependencies]
|
||||||
|
pretty_assertions = "0.4.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
File `src/lib.rs`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
// externing crate for test-only use
|
||||||
|
#[cfg(test)]
|
||||||
|
#[macro_use]
|
||||||
|
extern crate pretty_assertions;
|
||||||
|
|
||||||
|
pub fn add(a: i32, b: i32) -> i32 {
|
||||||
|
a + b
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add() {
|
||||||
|
assert_eq!(add(2, 3), 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
@ -0,0 +1,92 @@
|
|||||||
|
## Tests and ?
|
||||||
|
in Rust 2018, your unit tests can return Result<()>, which lets you use ? in them! This can make them much more concise.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn sqrt(number: f64) -> Result<f64, String> {
|
||||||
|
if number >= 0.0 {
|
||||||
|
Ok(number.powf(0.5))
|
||||||
|
} else {
|
||||||
|
Err("negative floats don't have square roots".to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sqrt() -> Result<(), String> {
|
||||||
|
let x = 4.0;
|
||||||
|
assert_eq!(sqrt(x)?.powf(2.0), x);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing panics
|
||||||
|
To check functions that should panic under certain circumstances, use attribute #[should_panic]. This attribute accepts optional parameter expected = with the text of the panic message. If your function can panic in multiple ways, it helps make sure your test is testing the correct panic.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn divide_non_zero_result(a: u32, b: u32) -> u32 {
|
||||||
|
if b == 0 {
|
||||||
|
panic!("Divide-by-zero error");
|
||||||
|
} else if a < b {
|
||||||
|
panic!("Divide result is zero");
|
||||||
|
}
|
||||||
|
a / b
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_divide() {
|
||||||
|
assert_eq!(divide_non_zero_result(10, 2), 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn test_any_panic() {
|
||||||
|
divide_non_zero_result(1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "Divide result is zero")]
|
||||||
|
fn test_specific_panic() {
|
||||||
|
divide_non_zero_result(1, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ignoring tests
|
||||||
|
Tests can be marked with the #[ignore] attribute to exclude some tests. Or to run them with command cargo test -- --ignored
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn add(a: i32, b: i32) -> i32 {
|
||||||
|
a + b
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add() {
|
||||||
|
assert_eq!(add(2, 2), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_add_hundred() {
|
||||||
|
assert_eq!(add(100, 2), 102);
|
||||||
|
assert_eq!(add(2, 100), 102);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn ignored_test() {
|
||||||
|
assert_eq!(add(0, 0), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
@ -0,0 +1,27 @@
|
|||||||
|
## Arc和Mutex结合实现多线程数据修改和汇总
|
||||||
|
```rust
|
||||||
|
use std::sync::{Arc,Mutex};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
struct JobStatus {
|
||||||
|
jobs_completed: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let status = Arc::new(Mutex::new(JobStatus { jobs_completed: 0 }));
|
||||||
|
let status_shared = Arc::clone(&status);
|
||||||
|
thread::spawn(move || {
|
||||||
|
for _ in 0..10 {
|
||||||
|
thread::sleep(Duration::from_millis(250));
|
||||||
|
let mut s = status_shared.lock().unwrap();
|
||||||
|
s.jobs_completed += 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
while status.lock().unwrap().jobs_completed < 10 {
|
||||||
|
println!("waiting... ");
|
||||||
|
thread::sleep(Duration::from_millis(500));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
@ -0,0 +1,10 @@
|
|||||||
|
## unknown `into` behavior
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let s: Box<dyn Error + Send + Sync> = "connection reset by peer".into();
|
||||||
|
```
|
||||||
|
|
||||||
|
this works because:
|
||||||
|
```rust
|
||||||
|
impl From<&'_ str> for Box<dyn Error>
|
||||||
|
```
|
Loading…
Reference in new issue