Rust by Example — страница 64 из 66

/// # // hidden lines start with `#` symbol, but they're still compileable!

/// # fn try_main() -> Result<(), String> { // line that wraps the body shown in doc

/// let res = try::try_div(10, 2)?;

/// # Ok(()) // returning from try_main

/// # }

/// # fn main() { // starting main that'll unwrap()

/// #    try_main().unwrap(); // calling try_main and unwrapping

/// #                         // so that test will panic in case of error

/// # }

/// ```

pub fn try_div(a: i32, b: i32) -> Result {

if b == 0 {

Err(String::from("Divide-by-zero"))

} else {

Ok(a / b)

}

}

See Also

   • RFC505 on documentation style

   • API Guidelines on documentation guidelines

Integration testing

Unit tests are testing one module in isolation at a time: they're small and can test private code. Integration tests are external to your crate and use only its public interface in the same way any other code would. Their purpose is to test that many parts of your library work correctly together.

Cargo looks for integration tests in tests directory next to src.

File src/lib.rs:

// Define this in a crate called `adder`.

pub fn add(a: i32, b: i32) -> i32 {

a + b

}

File with test: tests/integration_test.rs:

#[test]

fn test_add() {

assert_eq!(adder::add(3, 2), 5);

}

Running tests with cargo test command:

$ cargo test

running 0 tests


test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out


Running target/debug/deps/integration_test-bcd60824f5fbfe19


running 1 test

test test_add ... ok


test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out


Doc-tests adder


running 0 tests


test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Each Rust source file in tests directory is compiled as a separate crate. One way of sharing some code between integration tests is making module with public functions, importing and using it within tests.

File tests/common.rs:

pub fn setup() {

// some setup code, like creating required files/directories, starting

// servers, etc.

}

File with test: tests/integration_test.rs

// importing common module.

mod common;


#[test]

fn test_add() {

// using common code.

common::setup();

assert_eq!(adder::add(3, 2), 5);

}

Modules with common code follow the ordinary modules rules, so it's ok to create common module as tests/common/mod.rs.

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:

# standard crate data is left out

[dev-dependencies]

pretty_assertions = "0.4.0"

File src/lib.rs:

// 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);

}

}

See Also

Cargo docs on specifying dependencies.

Unsafe Operations

As an introduction to this section, to borrow from the official docs, "one should try to minimize the amount of unsafe code in a code base." With that in mind, let's get started! Unsafe annotations in Rust are used to bypass protections put in place by the compiler; specifically, there are four primary things that unsafe is used for:

   • dereferencing raw pointers

   • calling functions or methods which are unsafe (including calling a function over FFI, see a previous chapter of the book)

   • accessing or modifying static mutable variables

   • implementing unsafe traits

Raw Pointers

Raw pointers * and references &T function similarly, but references are always safe because they are guaranteed to point to valid data due to the borrow checker. Dereferencing a raw pointer can only be done through an unsafe block.

fn main() {

let raw_p: *const u32 = &10;

unsafe {

assert!(*raw_p == 10);

}

}

הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Calling Unsafe Functions

Some functions can be declared as unsafe, meaning it is the programmer's responsibility to ensure correctness instead of the compiler's. One example of this is std::slice::from_raw_parts which will create a slice given a pointer to the first element and a length.

use std::slice;

fn main() {

let some_vector = vec![1, 2, 3, 4];

let pointer = some_vector.as_ptr();

let length = some_vector.len();

unsafe {

let my_slice: &[u32] = slice::from_raw_parts(pointer, length);

assert_eq!(some_vector.as_slice(), my_slice);

}

}

הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

For slice::from_raw_parts, one of the assumptions which must be upheld is that the pointer passed in points to valid memory and that the memory pointed to is of the correct type. If these invariants aren't upheld then the program's behaviour is undefined and there is no knowing what will happen.

Compatibility