Unsafe in Rust
Amit Nadiger
Polyglot(Rust??, C++ 11,14,17,20, C, Kotlin, Java) Android TV, Cas, Blockchain, Polkadot, UTXO, Substrate, Wasm, Proxy-wasm,AndroidTV, Dvb, STB, Linux, Engineering management.
Rust is a systems programming language that prioritizes safety, memory safety, and concurrency. Rust's safety features come from its ownership and borrowing system, which prevents common bugs such as null pointer dereferences and data races. However, there are situations where we need to break the rules to implement certain functionality or to interface with low-level code. For these situations, Rust provides the unsafe keyword.
unsafe in Rust is a keyword that allows the programmer to bypass the safety checks imposed by the ownership and borrowing system. With unsafe, you can do things like dereference a raw pointer, modify a mutable static variable, or call a C function that has not been checked for safety.
While unsafe code can be powerful and necessary in some situations, it comes with certain risks and disadvantages. The main disadvantage is that unsafe code can cause undefined behavior and memory safety violations, leading to security vulnerabilities, crashes, or other unexpected behavior. This is because the safety checks that are normally enforced by the Rust compiler are not present in unsafe code, leaving the programmer responsible for ensuring that the code is safe.
However, there are several advantages to using unsafe code in Rust. One of the most important is that it allows you to interface with low-level code, such as C libraries, that may not have the same safety guarantees as Rust. By using unsafe code to interface with this code, you can still benefit from Rust's safety features while taking advantage of existing libraries and code.
Another advantage of unsafe code is that it can be used to optimize performance-critical code. Because unsafe code can bypass some of the safety checks imposed by Rust's ownership and borrowing system, it can be used to write low-level, high-performance code that would be difficult or impossible to implement safely in a higher-level language.
However, it is important to note that unsafe code should only be used when necessary, and with caution. When writing unsafe code, it is important to follow best practices and to use safe abstractions wherever possible. It is also important to thoroughly test unsafe code and to use tools such as sanitizers to catch any memory safety violations.
1st let me write the code without using the unsafe and lets see what happens :
In the below example, we define function that takes a raw pointer and writes a value to the memory location pointed to by the pointer. We then call this function from the main function without using the unsafe block.
fn unsafe_function() {?
? ? let ptr = 0xdeadbeef as *mut u32; // cast an integer to a raw pointer?
? ? *ptr = 42; // dereference the pointer and write a value?
}?
fn main() {?
unsafe_function(); // call the unsafe function?
}??
/*
Compilation error :
amit@DESKTOP-9LTOFUP:~/OmPracticeRust$ rustc UnsafeUsage.rs
error[E0133]: dereference of raw pointer is unsafe and requires unsafe function or block
?--> UnsafeUsage.rs:4:5
? |
4 |? ? ?*ptr = 42; // dereference the pointer and write a value
? |? ? ?^^^^^^^^^ dereference of raw pointer
? |
? = note: raw pointers may be null, dangling or unaligned; they can violate aliasing rules and cause data races: all of these are undefined behavior
warning: unnecessary `unsafe` block
?--> UnsafeUsage.rs:8:5
? |
8 |? ? ?unsafe {
? |? ? ?^^^^^^ unnecessary `unsafe` block
? |
? = note: `#[warn(unused_unsafe)]` on by default
error: aborting due to previous error; 1 warning emitted
For more information about this error, try `rustc --explain E0133`.
amit@DESKTOP-9LTOFUP:~/OmPracticeRust$
*/
Now lets use the unsafe as below :
unsafe fn unsafe_function() {
let ptr = 0xdeadbeef as *mut u32; // cast an integer to a raw pointer
*ptr = 42; // dereference the pointer and write a value
}
fn main() {
unsafe {
unsafe_function(); // call the unsafe function
}
}
/*
// Please note that no compilation error happed.
amit@DESKTOP-9LTOFUP:~/OmPracticeRust$ rustc UnsafeUsage.rs
amit@DESKTOP-9LTOFUP:~/OmPracticeRust$ ./UnsafeUsage
Segmentation fault (core dumped)
amit@DESKTOP-9LTOFUP:~/OmPracticeRust$
*/
In this example, we define an unsafe function that takes a raw pointer and writes a value to the memory location pointed to by the pointer. We then call this function from the main function using the unsafe block. Because the unsafe_function dereferences a raw pointer, it is marked as unsafe. By using the unsafe block to call this function, we tell the Rust compiler that we understand the risks and have taken the necessary precautions to ensure the safety of the code.
Please note above that Rust compiler allowed the unsafe code, but at the run time it resulted in to Segmentation fault (core dumped). So it is developer responsibility to handle the safty measures manually and diligently.
Advantages of using unsafe in Rust:
Disadvantages of using unsafe in Rust:
It is important to use unsafe only when necessary and to carefully weigh the benefits and risks before using it in production code. Additionally, it is good practice to carefully document any use of unsafe in your code to make it clear why it was necessary and what safety invariants must be maintained.
Here are some additional examples of unsafe functions in Rust:
Example usage:
let mut buffer = [0u8; 1024];
let ptr = buffer.as_mut_ptr();
let len = buffer.len();
// Create a mutable slice from the raw pointer and length
let slice = unsafe { std::slice::from_raw_parts_mut(ptr, len) };
The above code creates a mutable buffer of 1024 bytes with type [u8; 1024] and initializes it with all zeros.
Then, it obtains a raw mutable pointer to the beginning of the buffer by calling as_mut_ptr() method on the buffer.
After that, it gets the length of the buffer by calling the len() method on the buffer.
Finally, it creates a mutable slice using the from_raw_parts_mut() method from the std::slice module. This method takes a raw pointer to the first element in the buffer and the length of the buffer, and returns a mutable slice covering the entire buffer.
It is important to note that creating a raw pointer and using it to create a slice can be unsafe because Rust's safety guarantees do not extend to raw pointers. Therefore, this code should be used with caution and only when necessary.
----------------------------------------------->
2. std::ptr::copy: This function performs a byte-wise copy of a block of memory from one location to another. This function is often used in low-level memory manipulation.
Example usage:
let mut src = [1, 2, 3, 4, 5];//Has a length of 5 and contains the values [1, 2, 3, 4, 5].
let mut dst = [0; 5]; //Length of 5, contains the value 0 for all its elements.
// Copy the contents of `src` to `dst`
unsafe {
std::ptr::copy(src.as_ptr(), dst.as_mut_ptr(), src.len());
}
----------------------------------------------->
3. transmute: This function is used to reinterpret the bits of a value as a different type. This can be useful in situations where you need to treat a value as a different type.
Example usage:
// Create an integer value
let val: i32 = 42;
// Reinterpret the bits of `val` as a floating point value
let float_val: f32 = unsafe { std::mem::transmute(val) };
Let me list some examples of when unsafe code might be necessary without the code example :
It's important to note that while unsafe code can provide significant benefits in terms of performance and flexibility, they should be used with caution. The unsafe keyword indicates that the function has the potential to cause memory safety issues or other undefined behavior. It's important to thoroughly understand the risks and implications of using unsafe functions before incorporating them into your code.
Thanks for reading and please comment oif you have any questions or suggestions.
Let's learn together!!