What is unsafe keyword in Rust?
In Rust, a distinct part of the language is known as "unsafe Rust," which allows developers to bypass the memory safety guarantees enforced by the compiler. The unsafe keyword indicates code containing operations or invariants that cannot be fully verified at compile-time. The primary reasons for having unsafe Rust are as follows:
Conservative static analysis: Rust's compiler is conservative regarding safety checks. It may reject valid code to ensure memory safety. In such cases,
unsafeallows developers to inform the compiler that they will manually handle safety concerns.Interfacing with unsafe code: Rust allows developers to interface with low-level codebases, like C or C++ libraries, which may not provide the same safety guarantees as Rust. The
unsafecode is used when dealing with raw pointers, mutable static variables, and other low-level operations.Performance optimization: In some situations,
unsafecode can improve performance by avoiding certain safety checks. However, this should be done carefully and only after thorough analysis.
Features of the unsafe keyword
The unsafe keyword provides five major features:
Dereferencing raw pointers: Raw pointers (
*const Tand*mut T) can bypass borrowing rules and aren't guaranteed valid.Calling unsafe functions or methods: Unsafe functions or methods can be called from
unsafeblocks, indicating that the programmer takes responsibility for upholding their contracts.Accessing or modifying mutable static variables: It is inherently unsafe, and it must be done within an
unsafeblock.Implementing an unsafe trait: A trait can be marked as
unsafe, indicating that the compiler cannot verify certain invariants, and the implementor must ensure safety.Accessing fields of a union: Unions are similar to structs but can hold only one field at a time. Accessing fields within a union is considered unsafe due to the compiler's inability to ensure the data type contained in the union.
It's essential to grasp that using the unsafe keyword in Rust does not disable the borrow checker or any other safety checks enforced by the language. Rust will verify their validity even when working with references inside an unsafe block. The primary role of unsafe is to grant access to certain features not subject to the compiler's memory safety checks. However, Rust still offers a degree of safety within the confines of an unsafe block.
Moreover, it's of utmost importance to realize that the usage of unsafe doesn't inherently suggest that the code enclosed within the block is dangerous or will result in memory safety problems. Instead, the onus is on the programmer to guarantee that any code within an unsafe block is properly structured and complies with the regulations of memory safety.
Example
Following is an example of using and calling the unsafe function.
// Unsafe functionunsafe fn unsafe_access(arr: &[i32], index: usize) -> i32 {*arr.get_unchecked(index)}fn main() {let data = [10, 20, 30, 40, 50];// Normal indexinglet s_index = 2;let s_result = data.get(s_index).unwrap();println!("Safe access: {:?}", s_result);// Unsafe indexinglet u_index = 10;let u_result = unsafe { unsafe_access(&data, u_index) };println!("Unsafe access: {}", u_result);}
Explanation
The line-by-line explanation of the code above is as follows:
Line 2: We declare an
unsafefunction namedunsafe_access. The function takes two parameters: aarrofi32values and anindexof typeusize. The purpose of this function is to access elements of the slice directly without performing bounds checking.Line 4: We use the
get_uncheckedmethod on thearrto access the element at the givenindex. The*beforearr.get_unchecked(index)dereferences the pointer returned byget_unchecked, giving us the value at the specified index.Lines 7–8: We define the
mainfunction and create an arraydatacontainingi32values.Line 11: We declare a variable
s_indexand assign the value2to it.Line 12: Using the
.get()method on the arraydata, we attempt to access the element at the indexs_index.Line 13: We use
println!to print the result.Line 16: We declare a variable
u_indexand assign the value10to it.Line 17: We call the
unsafe_accessfunction using anunsafeblock. Inside theunsafeblock, we pass a reference to thedataarray and theu_indexto theunsafe_accessfunction. Sinceunsafe_accessassumes the index is within bounds, there are no bounds checking, and it directly accesses memory at the index10, which is out of bounds for thedataarray.
When to use unsafe
Developers should use unsafe judiciously and avoid it whenever possible. It should only be used when necessary for low-level operations, interfacing with external code, or performance optimization. It's crucial to thoroughly understand the implications and risks of using unsafe code and to keep unsafe blocks small and well-documented. Rust code should strive to provide safe abstractions over unsafe code whenever possible to minimize potential safety issues.
Free Resources