Go
If you have a background of C language, you must know what pointer means and its usage. With pointer, you are free to operate any data at memory level which means you have great power, but this means that you have great responsibility as well. That's why it might be considered unsafe in lots of cases.
import "fmt"
func double(x *int) {
*x += *x
x = nil
}
func main() {
var a = 3
double(&a)
fmt.Println(a) // 6
p := &a
double(p)
fmt.Println(a, p == nil) // 12 false
}
The major restrictions of pointer in GoLang are:
- No mathematic operations can be performed on pointer
- Cannot convert between different types of pointer
- Different types of pointer cannot be compared with == or !=
- Cannot assign one type of pointer to another type of pointer
GoLang Unsafe
There is also type unsafe pointer, it is unsafe.Pointer residing in unsafe package.
In unsafe package, there is a Pointer defined.
type Pointer *ArbitraryType
This is similar to void* in C. Also three additional functions are defined.
func Offsetof(x ArbitraryType) uintptr
func Alignof(x ArbitraryType) uintptr
Sizeof returned the number of bytes x takes, it doesn't count the actual number of bytes its content takes.
Offsetof returns the position where one member of a struct within the struct away from the beginning position of the struct
Alignof returns m which means the number of bytes which can be divided when align the memory in the struct.
unsafe package provide two important features:
- Any pointer can be converted to unsafe.Pointer and vice versa
- uintptr can be converted to unsafe.Pointer and vice versa
Get or update value of unexported property in struct
import (
"fmt"
"unsafe"
)
type Programmer struct {
name string
language string
}
func main() {
p := Programmer{"stefno", "go"}
fmt.Println(p)
name := (*string)(unsafe.Pointer(&p))
*name = "qcrao"
lang := (*string)(unsafe.Pointer(uintptr(unsafe.Pointer(&p)) + unsafe.Offsetof(p.language)))
*lang = "Golang"
fmt.Println(p)
}
Rust
Unsafe Superpowers
To switch to unsafe Rust, use the unsafe keyword and then start a new block that holds the unsafe code. You can take five actions in unsafe Rust, called unsafe superpowers, that you can’t in safe Rust. Those superpowers include the ability to:
- Dereference a raw pointer
- Call an unsafe function or method
- Access or modify a mutable static variable
- Implement an unsafe trait
- Access fields of unions
Dereferencing a Raw Pointer
Different from references and smart pointers, raw pointers:
Aren’t guaranteed to point to valid memory
Are allowed to be null
Don’t implement any automatic cleanup
we can create raw pointers in safe code, but dereference raw pointers and read the data being pointed to that requires an unsafe block.
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
unsafe {
println!("r1 is: {}", *r1);
println!("r2 is: {}", *r2);
}
Calling an Unsafe Function or Method
unsafe {
dangerous();
}
Creating a Safe Abstraction over Unsafe Code
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = slice.len();
let ptr = slice.as_mut_ptr();
assert!(mid <= len);
unsafe {
(
slice::from_raw_parts_mut(ptr, mid),
slice::from_raw_parts_mut(ptr.add(mid), len - mid),
)
}
}
Using extern Functions to Call External Code
Sometimes, your Rust code might need to interact with code written in another language. For this, Rust has a keyword, extern, that facilitates the creation and use of a Foreign Function Interface (FFI).
fn abs(input: i32) -> i32;
}
fn main() {
unsafe {
println!("Absolute value of -3 according to C: {}", abs(-3));
}
}
We can also use extern to create an interface that allows other languages to call Rust functions. Instead of an extern block, we add the extern keyword and specify the application binary interface (ABI) to use just before the fn keyword. We also need to add a #[no_mangle] annotation to tell the Rust compiler not to mangle the name of this function. Mangling is when a compiler changes the name we’ve given a function to a different name that contains more information for other parts of the compilation process to consume but is less human readable.
pub extern "C" fn call_from_c() {
println!("Just called a Rust function from C!");
}
Accessing or Modifying a Mutable Static Variable
Static variables are similar to constants. Static variables can only store references with the 'static lifetime, which means the Rust compiler can figure out the lifetime and we aren’t required to annotate it explicitly. Accessing an immutable static variable is safe.
Constants and immutable static variables might seem similar, but a subtle difference is that values in a static variable have a fixed address in memory. Using the value will always access the same data. Constants, on the other hand, are allowed to duplicate their data whenever they’re used.
Another difference between constants and static variables is that static variables can be mutable. Accessing and modifying mutable static variables is unsafe.
fn add_to_count(inc: u32) {
unsafe {
COUNTER += inc;
}
}
fn main() {
add_to_count(3);
unsafe {
println!("COUNTER: {}", COUNTER);
}
}
Implementing an Unsafe Trait
Another use case for unsafe is implementing an unsafe trait. A trait is unsafe when at least one of its methods has some invariant that the compiler can’t verify. We can declare that a trait is unsafe by adding the unsafe keyword before trait and marking the implementation of the trait as unsafe too.
// methods go here
}
unsafe impl Foo for i32 {
// method implementations go here
}
Accessing Fields of a Union
The final action that works only with unsafe is accessing fields of a union. A union is similar to a struct, but only one declared field is used in a particular instance at one time. Unions are primarily used to interface with unions in C code. Accessing union fields is unsafe because Rust can’t guarantee the type of the data currently being stored in the union instance