Unsafe Rust

Because of the inherent insecurity of the underlying computer hardware. If rust does not allow for unsafe operation, so some of the underlying task may simply not be completed, rust has not safe “super ability” to operate with the operating system, I understanding of rust itself is more than high degrees of freedom such as C, C + + language with a layer of security encapsulation, but safety is condition limitation, many functions cannot be achieved, You need to get rid of this restriction and use “insecure Rust” to implement it.

Dereference a bare pointer

The world of Rust has two new pointer types that are similar to references. They are both called raw Pointers. Like references, there are two types of raw Pointers:

// Immutable bare pointer
*const T
// Change the bare pointer
*mut T
Copy the code

Note that the asterisk at the beginning of a bare pointer is part of the type name and not the dereference operation

The difference between a bare pointer and a reference or a smart pointer:

  • It is possible to ignore borrowing rules, to have both mutable and immutable Pointers to the same memory address, or to have multiple mutable Pointers to the same address.
  • There is no guarantee that you will always point to a valid memory address.
  • Allowed to be empty.
  • No automatic cleanup mechanism is implemented.

Create an immutable bare pointer

We can create bare Pointers using type declarations and casts:

let num = 5;
// Type declaration is immutable bare pointer
let r1: *const i32 = #

// Use the as operator to convert a reference to a bare pointer:
let r2 = &num as *const i32;

println!("R1 memory address: {:? }", r1); R1 memory address: 0x7ffEE6B7323c
println!("R2 memory address: {:? }", r2); // R2 Memory address 0x7ffEE6B7323c
Copy the code

Since r1 and R2 are both created by num, the memory address is the same.

Create a mutable bare pointer

Mutable bare Pointers are the same as above, except that const is replaced by mut:

let r1: *mut i32 = &mut num;

/ / equivalent:
let r2 = &mut num as *mut i32;

println!("R1 memory address: {:? }", r1); R1 memory address: 0x7ffEE6B7323c
println!("R2 memory address: {:? }", r2); // R2 Memory address 0x7ffEE6B7323c
Copy the code

Creates a bare pointer to any memory address

We can directly convert a number to a bare pointer:

// 0x12345 is a hexadecimal number, which can also be written in decimal 74565
let address = 0x12345usize;
let r1 = &address as *const usize;
println!("R1 memory address {:? }", r1); R1 memory address: 0x7ffee6b73380

// Of course, mutable references and naked references are also possible,
// We use the decimal i32 type, but we just want to show that we can use any number type
let mut address2 = 74565i32;
let r2 = &mut address2 as *mut i32;
println!("R2 memory address {:? }", r2); // R2 Memory address 0x7ffEE6b73380
Copy the code

Raw pointer dereference

Bare Pointers support dereference and are used to retrieve the real data of the pointer, but the unsafe block is required:

let mut num = 5;
let r = &num as *const i32;

// Check the address
println!("{:? }", r); // 0x7ffee6b733dc

// Dereference data
println!("{}", *r); // An error was reported, unreferencing bare Pointers requires an unsafe block

// Dereference a bare pointer in an unsafe block
unsafe {
  println!("{:? }", *r); / / 5
}
Copy the code

Calling an unsafe function or method

Unsafe functions also need to be preceded by the unsafe keyword, which means unsafe operations in the function:

unsafe fn dangerous() - >i32 {
  let mut num = 5;
  let r = &num as *const i32;
  return *r
}
dangerous(); // An error was reported, unsafe functions are not allowed outside the unsafe block

// Called in the unsafe block
unsafe {
  let num = dangerous();
  println!("{}", num); / / 5
}
Copy the code

Create a security abstraction for insecure code

Just because a function contains unsafe code does not mean that we need to mark the entire function as unsafe. In fact, wrapping unsafe code in a secure function is a very common abstraction, as in the following example using the secure split_at_mut function:

let mut v = vec![1.2.3.4.5.6];
let r = &mutv[..] ;let (a, b) = r.split_at_mut(3);
assert_eq!(a, &mut [1.2.3]);
assert_eq!(b, &mut [4.5.6]);
Copy the code

The above code uses the split_at_mut function to split an array reference into two mutable references of the array by index. This function would be impossible to implement in a secure rust environment:

fn split_at_mut(slice: &mut [i32], index: usize) - > (&mut [i32], &mut [i32]) {
  let a = &mut slice[..index];
  let b = &mutslice[index..] ;// Error: cannot borrow slice multiple times with variable references
  return (a, b)
}
Copy the code

Unsafe blocks are unsafe because rust does not allow you to create two mutable references to the same data simultaneously:

fn split_at_mut(slice: &mut [i32], index: usize) - > (&mut [i32], &mut [i32]) {
  // Slice references consist of a starting position and a length

  // Get the length of the slice
  let len = slice.len();
  
  // Get a bare pointer to the starting position of the slice
  let ptr = slice.as_mut_ptr();

  // Ensure that the shard position cannot be larger than the slice length
  assert!(index <= len);

  // Slice start address in memory
  println!("Memory address: {:? }", ptr); // Memory address: 0x7ff765c05b20

  unsafe {
    use std::slice;

    // Dereference the start position of slice to get the first element
    println!("First element: {}", *ptr); // First element: 6

    // The first fragment, through the naked pointer position, directly in memory to obtain the specified length of the slice
    let a = slice::from_raw_parts_mut(ptr, index);
    println!("a: {:? }", a); // a: [6, 5, 4]

    // The length of the first fragment
    let move_to_index = ptr.offset(index as isize);
		
    // Use len-index to get the remaining length of the complete slice as the length of the second slice
    let b = slice::from_raw_parts_mut(move_to_index, len - index);
    println!("b: {:? }", b); // b: [3, 2, 1]

    (a, b)
  }
}
let mut v = vec![6.5.4.3.2.1];
let r = &mutv[..] ;let (a, b) = split_at_mut(r,3);
assert_eq!(a, &mut [6.5.4]);
assert_eq!(b, &mut [3.2.1]);
Copy the code

The split_at_mut function is not marked as unsafe in the code above, so it can be called in security Rust. The unsafe code is created as a safe abstraction from the unsafe code and implemented in a safe way because it only creates valid Pointers to access data, but sometimes the from_raw_parts_mut function can crash:

use std::slice;
let address = 0x12345usize;
let ptr = address as *const i32;
unsafe {
  let data: &[i32] = slice::from_raw_parts(ptr, 10000 as usize); // An error was reported. There is no guarantee that the slice of this code will always contain a valid i32 value
  println!("data: {:? }", data);
}
Copy the code

Since we only have the memory address, not the memory data, there is no guarantee that other variables will use the memory, and then we changed the value in the memory, so that the value in the memory is not a valid i32 type, so the compilation failed.

Use extern functions to call external code

In addition, Rust provides the extern keyword for intercalling with other languages to simplify the process of creating and using Foreign Function interfaces (FFI). FFI is a way for programming languages to define functions that allow other (external) programming languages to call:

// Declare the external function signature
extern "C" {
  fn abs(input: i32) - >i32;
}

// Called in the unsafe module
unsafe {
  abs(-10);
}

// Expose methods to other languages
// Use annotations to prevent Rust from changing its name at compile time
#[no_mangle]
pub extern "C" fn call_from_c () {
  println!("C calls this method.");
}
Copy the code

Access or modify a mutable static variable

In RUST, a global variable is also known as a static variable and defines and uses an immutable static variable:

static HELLO_WORLD: &str = "hello world";
println!("{}", HELLO_WORLD); // hello world
Copy the code

A constant and an immutable static variable may look very similar, but there is a subtle difference between them: the value of a static variable has a fixed address in memory, and using its value always accesses the same data. Constants, on the other hand, allow their data to be copied whenever they are used.

Static variables are allowed to be mutable

Static variables can also be marked mutable using mut, but can only be modified in an unsafe block:

static mut COUNTER: u32 = 0;

// COUNTER = 1; // An error is reported, and an unsafe block is required for use or modification

unsafe {
  COUNTER = 1;
  println!("{}", COUNTER); / / 1
}
Copy the code

Implement unsafe traits

A trait is unsafe when at least one method in a trait has unsafe factors that the compiler cannot verify. You also need to use broadening to identify that trait:

// Define and implement an unsafe trait
unsafe trait Foo {
  fn foo() {}}// Implement Foo trait for I32
unsafe impl Foo for i32 {
  fn foo() {}}Copy the code

Cover: Follow Tina to draw America

Read more about the latest chapter on our official account