CS3210 Design Operating Systems

Taesoo Kim





Rust 5: Generics and Traits

Taesoo Kim

Administrivia

Q&A

Given the benefits of traits in Rust, do traits have any disadvantages compared to interfaces or abstract classes?

How are objects in memory stored in Java or traditional OOP languages?

Q&A

Does rust allow for inheritance like many OOP do?

What’s the difference between an implementation of Ord and PartialOrd, which implements comparisons between two items? My only thought is this: a rock-paper-scissors game, where the following comparisons do not provide a hierarchy: rock > scissors, scissors > paper, paper > rock and it goes in a circle Is this a proper example of PartialOrd but not Ord implementation?

Agenda

Generics Overview

def sum(args):
  v = 0
  for a in args:
    v += a
  return v

sum([1, 2, 3, 4])         #=> 10
sum([1.0, 2.0, 3.0, 4.0]) #=> Q?

Generics Overview

fn sum(args: &[i32]) -> i32 {
    let mut v = 0;
    for &a in args {
        v += a;
    }
    return v;
}

sum(&[1, 2, 3, 4])         #=> 10
sum(&[1.0, 2.0, 3.0, 4.0]) #=> Q?

Generics Overview

fn sum1(args: &[i32]) -> i32 {
  let mut v = 0;
  for &a in args {
    v += a;
  }
  return v;
}

fn sum2(args: &[f32]) -> f32 {
  let mut v = 0.0;
  for &a in args {
    v += a;
  }
  return v;
}

Generics Overview

fn sum<T>(args: &[T]) -> T {
  let mut v =      /* Q. 0 or 0.0? */
  for &a in args {
    v += a;
  }
  return v;
}

Generics Overview

fn sum<T>(args: &[T]) -> T {
  let mut v =      /* Q. 0 or 0.0? */
  for &a in args { /* Q. which function should be invoked? */
    v += a;        /* Q. which "+=" should be executed? */
  }
  return v;        /* Q. what size? */
}

Trait Binding

fn sum<T>(args: &[T]) -> T
where
    T: Default + AddAssign + Copy,
{
    let mut v: T = Default::default();
    for &a in args {
        v += a;
    }
    return v;
}

Implementation of Generics

dbg!(sum(&[1, 2, 3, 4]));
dbg!(sum(&[1.0, 2.0, 3.0, 4.0]));

# test2::sum()
0000000000013e00 <_ZN5test23sum17h76c0ce64971bc830E>:
...
# test2::sum()
0000000000013ea0 <_ZN5test23sum17h8f28e3dbc449aa4aE>:
...

Traits (so far)

#[doc(alias = "+=")]
pub trait AddAssign<Rhs=Self> {
    /// Performs the `+=` operation.
    fn add_assign(&mut self, rhs: Rhs);
}

Example: Implementing Point

#[derive(Default, Copy, Clone, Debug)]
struct Point(i64, i64);

impl AddAssign for Point {
    fn add_assign(&mut self, rhs: Self) {
        self.0 += rhs.0;
        self.1 += rhs.1;
    }
}

dbg!(sum(&[Point(0, 0), Point(10, 10)]));

Example: Generic version of Point

#[derive(Default, Copy, Clone, Debug)]
struct Point<T>(T, T);

// Specializaion: implementing the AddAssign trait for types
// having the AddAssign trait
impl<T: AddAssign> AddAssign for Point<T> {
    fn add_assign(&mut self, rhs: Self) {
        self.0 += rhs.0;
        self.1 += rhs.1;
    }
}

dbg!(sum(&[Point(0.0, 0.0), Point(10.0, 10.0)]));

Example: Generic version of Point

#[derive(Default, Copy, Clone, Debug)]
struct Point<T1, T2>(T1, T2);

impl<T1: AddAssign, T2: AddAssign> AddAssign for Point<T1, T2> {
    fn add_assign(&mut self, rhs: Self) {
        self.0 += rhs.0;
        self.1 += rhs.1;
    }
}

dbg!(sum(&[Point(0, 0.0), Point(10, 10.0)]));

Trait Overview

Example: Summary trait

trait Summary {
    fn summary(&self) -> String;
}

impl<T1: Debug, T2: Debug> Summary for Point<T1, T2> {
    fn summary(&self) -> String {
        format!("Point({:?}, {:?})", self.0, self.1)
    }
}

impl<T> Summary for Vec<T> {
    fn summary(&self) -> String {
        format!("Vec({}/{})", self.len(), self.capacity())
    }
}

Static/dynamic dispatching

fn summarize0<T: Summary>(v: &T) {
    dbg!(v.summary());
}

// Same as above! i.e., static dispatching
fn summarize1(v: &impl Summary) {
    dbg!(v.summary());
}

// Dynamic dispatching using a virtual function table
fn summarize2(v: &dyn Summary) {
    dbg!(v.summary());
}

Static/dynamic dispatching

lea    rax,[rip+0x234b4]        # Point(1, 2)
mov    rdi,rax
call   4340 <test2::summarize0>

lea    rax,[rip+0x234a5]        # Point(1, 2)
mov    rdi,rax
call   45f0 <test2::summarize1>

lea    rax,[rip+0x23496]        # Point(1, 2)
lea    rcx,[rip+0x2f423]        # a virtual function table
mov    rdi,rax
mov    rsi,rcx
call   48a0 <test2::summarize2>

Trait vtable (ref. Cheatsheet)

00:0000│ rsi 0x555555587fb0 —▸ core::ptr::real_drop_in_place::hab7cd61d073ba88d
01:0008│     0x555555587fb8 ◂— 0x8
02:0010│     0x555555587fc0 ◂— 0x4
03:0018│     0x555555587fc8 —▸ _$LT$test2..Point$LT$T1$C$T2$GT$$u20$as$u20$test2..Summary$GT$::summary::h9d4aca369fc4e289)

Returning a Trait object

// Q1
fn new_summary(option: u32) -> Summary {}
// Q2
fn new_summary<T: Summary>(option: u32) -> T {}
// Q3
fn new_summary(option: u32) -> &Summary {}
// Q4
fn new_summary(option: u32) -> &dyn Summary {}

fn new_summary(option: u32) -> Box<dyn Summary> {}

Common Traits 1 (Lab2)

pub trait Debug {
  fn fmt(&self, f: &mut Formatter) -> Result;
}

Common Traits 2 (Lab2)

Sorry, then is Rust helpful?

Benefit of implementing Read

fn main() -> std::io::Result<()> {
    let f = File::open("log.txt")?;
    let mut reader = BufReader::new(f);

    let mut line = String::new();
    let len = reader.read_line(&mut line)?;
    println!("First line is {} bytes long", len);
    Ok(())
}

Traits and Rust language are tightly coupled

Common Traits

pub trait Into<T> {
    fn into(self) -> T;
}

impl<T, U> Into<U> for T
where
    U: From<T>,
{
    fn into(self) -> U {
        U::from(self)
    }
}

assert_eq!("Hello".into(), String::from("Hello"));

Iterator and IntoIterator

pub trait Iterator {
  // associated types
  type Item;
  fn next(&mut self) -> Option<Self::Item>;
}

pub trait IntoIterator {
  // associated types
  type Item;
  type IntoIter: Iterator<Item=Self::Item>;

  fn into_iter(self) -> Self::IntoIter;
}

Example: impl Iterator for Point

struct IterPoint<T>(Point<T, T>, T);

// e.g., Specializing it to Point<i32, i32>
impl Iterator for IterPoint<i32> {
    type Item = Point<i32, i32>;

    fn next(&mut self) -> Option<Self::Item> {
        let rtn = Some(self.0);

        (self.0).0 += self.1;
        (self.0).1 += self.1;

        rtn
    }
}

Example: impl IntoIterator for Point

impl IntoIterator for Point<i32, i32> {
    type Item = Point<i32, i32>;
    type IntoIter = IterPoint<i32>;

    fn into_iter(self) -> Self::IntoIter {
        IterPoint(self, 1)
    }
}

Example: impl IntoIterator for Point

let line = Point(0, 0).into_iter();
let even = line.step_by(2);
let quad = even.filter(|x| x.0 % 4 == 0)
    
for i in quad.take(10) {
  dbg!(i);
}

References