Skip to content

Instantly share code, notes, and snippets.

@jki127
Last active November 20, 2019 18:03
Show Gist options
  • Save jki127/a757f60eeee7866af4c15f95d8410b3f to your computer and use it in GitHub Desktop.
Save jki127/a757f60eeee7866af4c15f95d8410b3f to your computer and use it in GitHub Desktop.

Smart Pointers & Rust Intro - Programming Languages - Nov 19th, 2019

Monad Review

Why does Haskell only have to use the readFile function to read a file?

Imperative languages usually have open, read, and close. This is because files can be bigger than available memory and we want to be able to read files in chunks.

Haskell can use just readFile because it's lazy! It only reads data from a file only when you try to access it.

Something to think about: There is a disadvantage to lazy file access. Think about when Haskell is closing the file.

Smart Pointers

Smart Pointers are one alternative to garbage collection.

class Foo {
	int *pint;

public:
	Foo() {pint = new int(3);}
	~Foo() {delete pint;}
}

int main() {
	Foo foo;
	cout << "Hello" << endl;
	return 0;
}

Let's make this more generic.

template <class T>
class smartptr {
	T *value;

public:
	smartptr(T *x) value(x) {}
	~smartptr() {delete value;}

	T &get() {
		return *value;
	}
	T *operator*(){
		return *value;
	}
	T *operator->(){
		return value;
	}
}

class MyThing {
public:
	MyThing() {cout << "Making mything" << endl;}
	~MyThing() {cout << "Destroying mything" << endl;}
	void usefulFunction() {
		cout << "Soooo useful" <<endl;
	}
}

void test(smartptr<MyThing> thething) {
	thething->usefulFunction();
}

int main() {
	smartptr<MyThing> smartptr(new MyThing);
	cout << "Hello" << endl;
	foo->usefulFunction();
	return 0;
}

This code will lead to a Double Free because in test a shallow copy of smartptr is made. This is because the shallow copy just copies the address of our smartptr's value.

We can prevent C++ from creating a copy constructor by putting following code inside smartptr:

smartptr(const smartptr<T>&) = delete;
const smartr&operator=(const smartptr&) = delete;

This fixes our double free issue, but not in a useful way.

Reference Count

We can use a variable count to keep track of the number of smartptrs that are made.

template <class T>
class smartptr {
	T *value;
	int count;

public:
	smartptr(T *x) value(x), count(1) {}
	
	// new destructor
	~smartptr() {
		count--;
		if (count == 0){
			delete value;
		}
	}

	// custom copy constructor
	smartptr(const smartptr<T>&x){
		this->value = x.value;
		this->count = x.count + 1;
		x.count++;
	}
	
	// disable assignment operator
	const smartptr&operator=(const smartptr&) = delete;

	T &get() {
		return *value;
	}
	T *operator*(){
		return *value;
	}
	T *operator->(){
		return value;
	}
}

class MyThing {
public:
	MyThing() {cout << "Making mything" << endl;}
	~MyThing() {cout << "Destroying mything" << endl;}
	void usefulFunction() {
		cout << "Soooo useful" <<endl;
	}
}

void test(smartptr<MyThing> thething) {
	thething->usefulFunction();
}

int main() {
	smartptr<MyThing> smartptr(new MyThing);
	cout << "Hello" << endl;
	foo->usefulFunction();
	return 0;
}

The problem with the above code is that when we make new MyThing objects, count won't be updated for all the objects. The first object will have 1, second will have 2, etc.

We fix this by creating count on the heap and sharing it between all MyThing objects.

template <class T>
class smartptr {
	T *value;
	int *count;

public:
	smartptr(T *x) value(x), count(new int(1)) {}
	
	~smartptr() {
		count--;
		if (count == 0){
			delete value;
		}
	}

	smartptr(const smartptr<T>&x){
		this->value = x.value;
		this->count = x.count;
		(this->count)++;
	}
	

	const smartptr&operator=(const smartptr&) {
		if (this == &x){
			return *this;
		}
		(*this->count)--;
		if(*count==0){
			delete count;
			delete value;
		}
		this->value = x.value;
		this->count = x.count;
		(*this->count)++;
		return *this;
	}

	T &get() {
		return *value;
	}
	T *operator*(){
		return *value;
	}
	T *operator->(){
		return value;
	}
}

class MyThing {
public:
	MyThing() {cout << "Making mything" << endl;}
	~MyThing() {cout << "Destroying mything" << endl;}
	void usefulFunction() {
		cout << "Soooo useful" <<endl;
	}
}

void test(smartptr<MyThing> thething) {
	thething->usefulFunction();
}

int main() {
	smartptr<MyThing> smartptr(new MyThing);
	cout << "Hello" << endl;
	foo->usefulFunction();
	return 0;
}

Shared & Unique Pointers in C++

class MyThing {
public:
	MyThing() {cout << "Making mything" << endl;}
	~MyThing() {cout << "Destroying mything" << endl;}
	void usefulFunction() {
		cout << "Soooo useful" <<endl;
	}
}

int main() {
	MyThing *whatever = new MyThing;
	// shared_ptr<MyThing> foo = make_unique(*whatever);
	shared_ptr<MyThing> foo = make_shared(*whatever);
	foo->usefulFunction();
	return 0;
}

A unique pointer is like our first example where we disabled our copy constructor.

A shared pointer is like our second example where we shared the reference count between all the objects.

Rust

int &some_func() {
	int q = 42;
	return q;
}

int main() {
	int &v = some_func();
	cout << v << endl;
	return 0;
}

It would be great if compilers would be able to catch dangling pointers like the one in the code above.

fn some_func() -> &i32 {
	let q : i32 = 42;
	return &q;
}

fn main() {
	let v = some_func();
	println!("{}", v);
}

If we try to compile this with rustc it will give us an error because the lifetime of q is only within some_func.

Will this code compile?

fn some_func(p: &i32) -> &i32 {
	let q : i32 = p;
	return q;
}

fn main() {
	let v = some_func(&99);
	println!("{}", v);
}
Answer: Yes, it will compile because Rust knows that `q` equals a reference to `v` which still has a lifetime after `some_func`.

Defining Lifetime

In Rust we can define the lifetime of return values.

fn some_func(p: &i32) -> &'a i32 {
	let q : i32 = p;
	return q;
}

fn main() {
	let v = some_func(&99);
	println!("{}", v);
}

Notice that we added &'a in the definition of some_func. This means that the returned value will have some lifetime a.

We can also say &'static to say that a return value has a static lifetime (just like static in C++).


Take a look at this C++ code

struct Foo {
	Foo(int &someint) : x(someint) {}
	int &x;
}
Foo f1() {
	int q = 9;
	Foo foo(q);
	return foo;
}

int main(){
	Foo myfoo = f1();
	return 0;
}

At the end of the program, myfoo has a variable x which points to a dangling pointer.

Let's rewrite this in Rust.

struct Foo<'a> {
	x: &'a i32
}

fn f1<'b>() -> Foo<'b> {
	let q : i32 = 9;
	let foo : Foo = Foo{x:&q};
	return foo;
}

fn main() {
	let myfoo : Foo = f1();
}

#school/f19/proglang-f19

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment