POINTERS

 POINTERS

  • Stack is LIFO.
  • When your functions returns to the base function, the value in the stack is not deleted, the stack just moves its pointer to a higher point.
  • Heap is random access.
  • A pointer is a general concept for a variable that contains an address in memory. This address refers to some other data.
  • Smart pointers are data structure that acts like a pointer but also have additional metadata and capabilities.
  • Unlike normal reference, smart pointer themselves own the data they point to.

     Box<T>

  • Box <T> is one of those smart pointers in rust, which is used allocating values in heap. i.e it can be used to store even integer in heap.
  • fn main(){
      let age:Box<i32> = Box::new(10);
      let double_age: i32 = *age * 10;
      println!("{}", age);
      println!("{}", double_age);
    }
  • The above code block shows a basic example of box usage.
  • As you can see here the integer is stored in heap with * dereferences unary operator. It extracts the value of the variable, omiitting the pointer or reference.
  • Deref is a trait that a rust can use to dereference the box.
  • use::std::ops::Deref;
    
    struct BoxedValue<T>{
      value: T
    }
    
    impl<T> Deref for BoxedValue<T>{
      type Target = T;
      fn deref(&self) -> &self::Target{
        &self.value
      }
    }
    
    fn main(){
      let age:Box<i32> = BoxedValue::new(10);
      let double_age: i32 = *age * 10;
      println!("{}", age);
      println!("{}", double_age);
    }
  • The above block shows the implementation of Box on our own.
  • fn main(){
      let age:Box<i32> = BoxedValue::new(10);
      let double_age: &i32 = age.deref();
      println!("{}", double_age);
    }
  • Although deref method and * may look like it is providing the same functionality, it's really not.
  • deref method gives a reference to the boxed value whereas * provides the actual value.
  • fn print_integer(value: &i32){
      println!("{}", value);
    }
    fn main(){
      let age: Box<i32> = BoxedValue::new(10);
      let test: &Box<i32> = &age;
      print_integer(&age);
    }
  • In the above example you can see that when you are passing a reference of a boxed value, the code is working perfectly without any issue it is called implicit deref coercion.

     Rc<T> (Reference counted)

  • Used to enable multiple ownership explicitly by using Rust Type Rc<T>. It keeps track of the number of references to a value to determine whether or not the value is still in use.
  • Rc is single threaded. i.e, it cannot be passed from one thread to another.
  • It disallows mutation of wrapped value.
  • You call also create a weak reference, as long the objects exist, the reference is also available but when the objects is removed the weak reference will throw an error.
  • use ::std::rc::Rc;
    fn main(){
       let array: Vec<String> = vec!["Sanu Shilshad".to_string()];
       let rc: Rc<Vec<String>> = Rc::new(array);
       let weak: Weak<Vec<String>> = Rc::downgrade(this: &rc);
       drop(rc);
       let value: Rc<Vec<String>> = weak.upgrade().unwrap();
       println!("{:?}", value);
    }
  • The above example shows an example of the working on of weak reference, and the result would be an error as the rc is already removed.
  • In the above example upgrade method returns a option and you can use a match on it like below:

  • use ::std::rc::Rc;
    fn main(){
       let array: Vec<String> = vec!["Sanu Shilshad".to_string()];
       let rc: Rc<Vec<String>> = Rc::new(array);
       let weak: Weak<Vec<String>> = Rc::downgrade(this: &rc);
       drop(rc);
       match weak.upgrade(){
         some(rc) => println!("{:?}", rc),
         None => println!("None") 
       }
    }
  • use ::std::rc::Rc;
    fn main(){
       let array: Vec<String> = vec!["Sanu Shilshad".to_string()];
       let rc: Rc<Vec<String>> = Rc::new(array);
       let rc2: Rc<Vec<String>> = rc.clone();
       drop(rc);
       println!("{:?}", rc2);
    }
  • In the above example rc2 will have have reference to the array even if the rc is dropped unlike weak reference.
  • You can't mutate value stored inside an Rc, it is not possible in Rc, it there anther type called Cell which allow mutability.

    Cell<T>

  • This type provides interior mutability, i.e, it allows you to mutate data even when there are immutable reference to that data.
  • This type holds single ownership over the data it holds.
  • This is highly unsafe.
  • use ::std::cell::Cell;
    
    struct Person{
      name: String,
      age: Cell<u8>
    }
    
    impl Person {
      fn increment_age(&self) -> u8{
        self.age.set(val:self.age.get() + 1);
        self.age.get()
      }
    } 
    
    
    fn main(){
      let person: Person = Person{
          name: "Sanu Shilshad".to_string(),
          age: Cell::new(20)
      }
      let new_age: u8 = person.increment_age();
      println!("{}, new_age);
      println!("{}, person.age.get());
    }
  • The above example shows a basic implementation of Cell.
  • RefCell a much safer type which allows multiple immutable borrows or one mutable borrow.
  • With type box, the borrowing rules are enforced during compile time. While with type Refcell it is enforced during runtime.
  • Refcell is only allowed in single threaded environment.
  • use ::std::cell::RefCell;
    
    fn main(){
      let ref_cell: RefCell<Vec<i32>> = RefCell::new(vec![1, 2, 4]);
      let mut mut_ref: RefMut<Vec<i32>> = ref_cell,borrow_mut();
      let len: usize = ref_cell.borrow().len();
      println("{}", len);
      mut_ref.push(100);
    }
  • The above example will throw a panic at run time well as we have called it twice, a mutable and an immutable one, thus breaking RefCell's rule.

Comments