Sunday, June 14, 2020

Pointers in Rust

pointers in Rust

Pointers

Pointers - what is the definition of pointer?
As per wikipedia - In computer science, a pointer is a programming language object that stores a memory address. This can be that of another value located in computer memory, or in some cases, that of memory-mapped computer hardware.

https://en.wikipedia.org/wiki/Pointer_(computer_programming)

Historically pointers become a tough topic for students, even though the definition looks simple. The loop holes behind the pointers make programmers life hard with them.
If I write a program in C with pointers, its end up in “Segmentation Fault”

Pointers in Rust

I have seen Computer languages started introducing smart pointers recently for safety . So since Rust is new language, Rust has really smart pointers .But it may looks a bit more syntax around them. Once you know the specification of them its quite safe to use them.

1.Raw pointers in Rust

Let starts with dirty one(it’s not dirty,just not smart). Raw, unsafe pointers, *const T, and *mut T.
As name suggests they are raw or unsafe pointers. You can assign a raw address to a raw pointer.

These pointers are very useful when you need to assign some port address to a variable in Rust.

fn main() {
    let p: *mut i32;
    p = 0x10 as *mut i32 ;
    
}

But if you try to deference and assign a value , it throws error in Rust. Rust error shows you why it is risky…

```rust_errors
|
5 |     *p= 10;
  |     ^^^^^^ 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

But if you explicitly tell the compiler that I am sure this is safe to use and so am tagging this snippet under “unsafe” , the compiler compile the code successfully
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=792e09bd5485af4ceca96e59ba80848b

Read more: https://doc.rust-lang.org/std/primitive.pointer.html

2.references - &T

References are generally called as pointers. In Rust,&T type a ‘reference’. So rather than calling this as pointers ,Rust consider this as a separate Type.
&T means reference to a type value T. You can dereference it with * operator.

fn main() {
    let a = 10;
    let p = &a;
    println!("{}",*p);
    
}

3.References - &mut T

Since it is a Type , &T and &mut T are different. The first one is simple reference and we can access the value that points ,but can’t modify the value.
You can modify the value with &mut T reference
Rust treats different variation(mutable or immutable) of references as different types
eg:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=776367d50a05cc1af60c90b31d8ac0d7

But mutable references have one big restriction: you can have only one mutable reference to a particular piece of data in a particular scope

The Rules of References

  • At any given time, you can have either one mutable reference or any number of immutable references.
  • References must always be valid.
fn main() {

    let mut a = 10;
    
    let p1 = &mut a;
    
    *p1 = 11;
   
    let p2 = &a;
    
    println!("{},{}",*p1,*p2)
    
}

Above code won’t compile with error message

rust_errors
error[E0502]: cannot borrow `a` as immutable because it is also borrowed as mutable
  --> src/main.rs:9:14
   |
5  |     let p1 = &mut a;
   |              ------ mutable borrow occurs here
...
9  |     let p2 = &a;
   |              ^^ immutable borrow occurs here
10 |     
11 |     println!("{},{}",*p1,*p2)
   |                      --- mutable borrow later used here

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=1a975610b3d5d380e57dea9cc1c286b4

If you use 2 mutable reference, the program won’t compile successfully.

fn main() {

    let mut a = 10;
    
    let p1 = &mut a;
    
    *p1 = 11;
   
    let p2 = &mut a;
    
    println!("{},{}",*p1,*p2)
    
}

error message is below, hope that is self explanatory.

error[E0499]: cannot borrow `a` as mutable more than once at a time
  --> src/main.rs:9:14
   |
5  |     let p1 = &mut a;
   |              ------ first mutable borrow occurs here

9  |     let p2 = &mut a;
   |              ^^^^^^ second mutable borrow occurs here
10 |     
11 |     println!("{},{}",*p1,*p2)
   |                      --- first borrow later used here

4.Box - A pointer type for heap allocation.

A Box allows heap allocations similar to “malloc” in C programming.
You can mutate the heap value using the keyword ‘mut’ in front of the variables.

fn main() {

   let mut heap_pointer = Box::new(10);
    *heap_pointer += 1;
   println!("{}",*heap_pointer);
   
}

The above code gives you the output as 11.

 fn main() {

    let mut heap_pointer = Box::new(10);
    
    let heap_pointer2 = &mut heap_pointer ;

   // let heap_pointer3 = &mut heap_pointer;
   // heap_pointer2 = *heap_pointer2 + 1;
   
   match *heap_pointer {
       x => { println!("{}",x+34)},
   }
    
 //   let heap_pointer3 = &mut heap_pointer;
  //   *heap_pointer2 = *heap_pointer + 1;
    println!("{}",*heap_pointer);
 //   println!("{}",x);
 //   println!("{}",*heap_pointer2);
    
}

How to get the value from Box:
You can use * operator to dereference the address.

#![allow(unused)]
fn main() {
let x = Box::new(String::from("Hello"));
println!("{}",*x);
}

Using Raw pointer -

You have to use “unsafe” block for creating a raw pointer for a boxed value.

#![allow(unused)]
fn main() {
let x = Box::new(String::from("Hello"));

//taking a raw pointer
let ptr = Box::into_raw(x);

let x = unsafe { Box::from_raw(ptr) };

println!("{}",x);
}

5.Rc - Reference Counted

We are getting into the world of smart pointers. Reference counting is not a new concept, it is there in Python. If you see below program in python tutor.

a = [12,3,4]
b = a
c = b

a = 0
b= 1
c =2

The python visualizer shows you that the object (list object) remains in the memory until all the references to the object get removed.
Note: when we assign a =0 , the value assigned to ‘a’ become zero .

http://www.pythontutor.com/visualize.html#code=a %3D [12,3,4] b %3D a c %3D b a %3D 0 b%3D 1 c %3D2 &cumulative=false&curInstr=6&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=3&rawInputLstJSON=[]&textReferences=false

In Rust ‘Rc’ is a single-threaded reference-counting pointer. ‘Rc’ stands for ‘Reference Counted’.

Below program creates an Rc reference and get the value using * operator (dereferencing )



fn main() {
use std::rc::Rc;

let x = Rc::new("hello".to_owned());

let y = Rc::clone(&x);
let z = Rc::clone(&x);

println!("{}",*x);
println!("{}",*y);

println!("{}",Rc::strong_count(&y));

}

Below Rust program generates number of references from a heap allocated memory . You might have noticed that we drop ‘x’ in the middle. But still we have 2 Rc references. If you have multiple normal reference &T for a heap allocation, compiler won’t allow you to drop the owned reference.

fn main() {
use std::rc::Rc;
let x = Rc::new("hello".to_owned());

let y = Rc::clone(&x);
let z = Rc::clone(&x);

drop(x);

println!("{}",Rc::strong_count(&x));
}

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=12748f50761fb40ae36bead095f64a65

6.Recell

A mutable memory location with dynamically checked borrow rules.
Refcell gives you a mutable reference for an immutable instance.
if you do two or more ‘borrow_mut’ in the same scope, the program panic in the run time.

But below program , we are passing borrow_mut to the function, so it is not in the same scope.

#![allow(unused)]
use std::cell::RefCell;
use std::cell::*;

fn main() {

//c is not declared as mutable.
let  c = RefCell::new(5);

//but we are creating a mutable borrow and passing to the function.
fun (c.borrow_mut());
fun (c.borrow_mut());

println!("{}",c.into_inner());

}

//fun function receive a mutable cell
fn fun(mut x:RefMut<i32>){
    
// changing the value inside the cell, even though cell 'c' was not declared as mutable.
    *x = 3;
}

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=02b0ed5116b1abada7160c6ee0fbb436

You can wrap RefCell(not declared mutable) with Rc and mutate the object with Rc reference . If you are using immutable Rc for another objects other than RefCell, it won’t allow to mutate .

#![
allow(unused)]
use std::cell::RefCell;
use std::cell::*;
use std::rc::Rc;
fn main() {

//c is not declared as mutable.
let  c = RefCell::new(5);

//wrap Refcell with Rc

let r = Rc::new(&c);
//wrap Refcell with Rc, even though r1 is not mutable , we use it mutating cell value
let r1 = Rc::clone(&r);

let r2 = Rc::clone(&r);



//but we are creating a mutable borrow and passing to the function.
fun (r1.borrow_mut());

//let d = c.borrow();
fun( r2.borrow_mut());


println!("{}",c.into_inner());

}

//fun function receive a mutable cell
fn fun(mut x:RefMut<i32>){
    
   // changing the value inside the cell, even though cell 'c' was not declared as mutable.
    *x += 3;
} 

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=75d5ccbb4c6c9d62d5df4a92a8f7137e

Conclusion

You might need to try these pointers and write some small code snippets and see to understand how Rust compiler behaves on these pointers. The smart pointers really safe to use in Rust. You can avoid bugs that are hard to debug like race condition, double free,free after use etc…

This is just my notes and experiments with Rust pointers.There could be some missing or incorrect information. Happy to correct it !!

1 comment:

Naveen Davis said...

I highly recommend to watch below youtube video tutorial for Rust pointers.

Crust of Rust: Smart Pointers and Interior Mutabilit