Newer
Older
\chapter{Introduction}
\section{The Rust Ownership and Borrow System}
\subsection{Ownership}
In most programming languages, one can freely create and delete resources. For example, in C the programmer allocates memory on the heap with \inline{malloc} and frees the memory when they don’t need the memory anymore using \inline{free}.
\begin{lstlisting}[language=C, caption={Memory management in C}]
// Allocate space for an `int`.
int *ptr = (int*) malloc(sizeof(int));
Even in languages without manual memory management, the programmer still must manage other resources. For example, to work with a file in Python one could write:
\begin{lstlisting}[language=Python, caption={Manual resource management in Python}]
# Open the file and store the file handle in a variable.
file = open("path/to/some.file", "r")
# ...
# Close the file
file.close()
\end{lstlisting}
\subsubsection{Resource Handling Strategies}
This manual resource management comes with some difficulties. The programmer has to watch out to always free the resources after using them, because otherwise the program could leak memory or unnecessarily inhibit other processes from accessing the resources. On the other hand, one may also not free the resources too early, which would lead to a use after free, and also not free resources multiple times. This can be very tricky in complex programs where resources are shared between threads or over \acs{API} boundaries.
That is why several programming languages have ways to make handling resources easier. Python has context managers that close resources automatically.
\begin{lstlisting}[language=Python, caption={Context managers in Python}]
with open("path/to/some.file", "r") as file:
#...
# At this point `file` is closed.
\end{lstlisting}
This code is equivalent to the previous one, but even better: It also closes the file in case of an exception. Programmers can also define custom context managers for their own resources.
Other languages have different strategies. Go supports the \inline{defer} statement which takes an operation that is not to be run immediately but at the end of the scope.
\begin{lstlisting}[language=Go, caption={The \inline{defer} statement in Go}]
file, err := os.Open("path/to/some.file")
defer file.Close()
// ...
} // Block ends here; run the deferred close file
\end{lstlisting}
These prevent double frees and use after free errors, but they can still be forgotten. A programmer who wants to use a resource must be aware that it has to be cleaned up
in the end and changes in one line of the code have to be mirrored in the other. It is not as bad as in manual resource management because the programmer has to change only one additional line and that line is usually close.
\subsubsection{\acs{RAII} and Drop Responsibilities}
A third variant is \acfi{RAII}, also called \acfi{SBRM}, used by C++ and Rust. This ensures that the destructor of an object is run when it goes out of scope, which will clean up automatically. An example is the \inline{std::unique_ptr} in C++. The pointer \emph{owns} the memory it points to, meaning when it is created, it automatically allocates memory on the heap and when it goes out of scope, it frees the memory.
\begin{lstlisting}[language=C++, caption={\ac{RAII} in C++}]
{ // Block starts here.
std::unique_ptr<int> ptr = std::make_unique<int>(42);
// ...
} // Block ends here; run the destructor and free the memory
\end{lstlisting}
\ac{RAII} ensures that resources are always freed when they are not live (may not be used) anymore. The programmer can’t forget to free the resources nor cause use after free or double free. While in C++ only objects conform to \ac{RAII}, in Rust every value is owned by a variable. For example, the Rust analog to an owning pointer is \inline{Box}.
\begin{lstlisting}[language=Rust,caption={Heap allocation in Rust}]
{ // Block starts here.
let ptr = Box::new(42)
// ...
} // Block ends here; drop value and free memory
\end{lstlisting}
As soon as a variable falls out of scope, it is \emph{dropped}, meaning all
associated resources are freed. It is said that a variable has a \emph{drop responsibility}. A programmer can define custom drop behavior for their own resources by implementing the \inline{Drop} trait for their type, but normally the compiler does it for us.
\subsubsection{Move Semantics}
\ac{RAII} comes with a few disadvantages, though. Since a value is dropped when its owner goes out of scope, there must always be exactly one owner for every value. This means that the compiler will transfer ownership sometimes, like in the following example.
\begin{lstlisting}[language=Rust,caption={Ownership transfer},label={lst:ownership-transfer}]
fn print_value(ptr: Box<i32>) {
println!("{}", *ptr);
}
fn main() {
*ptr += 1;
}
\end{lstlisting}
We create a pointer and use it as an argument to a function that prints the pointee’s value. After that we increment the value. But if we try to compile this, we get an error:
error[E0382]: use of moved value: `*ptr`
\end{lstlisting}
Because we supplied \inline{ptr} as an argument to \inline{print_value}, it took ownership of that value, and now has the drop responsibility. As soon as the call to \inline{print_value} is finished, the memory is freed and accessing it afterwards is a use after free. We say that the value was \emph{moved} out of \inline{ptr} and into \inline{print_value}.
\subsubsection{Clone}
We can still make it work by \emph{cloning} \inline{ptr} and supply the clone as an argument. \inline{ptr.clone()} creates a new \inline{Box}, allocates new memory, and initializes it with the same value as \inline{ptr}’s:
\begin{lstlisting}[language=Rust, caption={Usage of \inline{Clone}}]
fn print_value(ptr: Box<i32>) {
println!("{}", *ptr);
}
fn main() {
let ptr = Box::new(42);
print_value(ptr.clone());
*ptr += 1;
Now everything works but keep in mind that the clone is completely independent from the original value. If we changed the value of the clone in \inline{print_value}, it would not be visible to the outside.
\subsubsection{Copy}
We previously noted that ownership, and therefore move semantics, apply to every value in Rust. But if we change the code to use an \inline{i32} directly instead of putting it on the heap, we don’t need to clone anything:
\begin{lstlisting}[language=Rust, caption={Number types are \inline{Copy}}]
fn print_value(number: i32) {
println!("{}", number);
}
print_value(number);
number += 1;
This compiles and works. That is not because \inline{i32} breaks move semantics but because the type implements the \inline{Copy} trait. Normally, if a value is moved, the physical bits making up that value are moved to a new location, e. g. the new stack frame, and are not available at the previous position anymore. If a type implements \inline{Copy}, the bit pattern is instead copied over and retained. Because \inline{i32} doesn’t allocate any heap or handles any resources, such a copy is valid. A \inline{Box} is not \inline{Copy}, because then two owners for the same resource would exist which would violate drop responsibility. If we want to duplicate a \inline{Box}, we need to allocate new memory on the heap and initialize it properly, which is what \inline{clone} does.
\subsubsection{ManuallyDrop}
Sometimes we don’t want \ac{RAII} to happen, we want to free resources ourselves. If that is the case, we can wrap a value in a \inline{ManuallyDrop} which tells the compiler to not drop it for us. This is a general trend in Rust: The correct way should be the easiest to do, and all potentially unsafe constructs are opt-in. More information on \inline{ManuallyDrop} can be found in the documentation for the type.\footnote{\url{https://doc.rust-lang.org/stable/std/mem/struct.ManuallyDrop.html}}
As we have seen, ownership and move semantics ensure memory safety. But they are not easy to use, \inline{clone}ing data has a runtime overhead, and many correct programs are rejected. For example, \autoref{lst:ownership-transfer} describes a program that would work if it were not for move semantics.
That is where references and borrowing come in. Instead of taking ownership of a value, a function can only borrow it through a reference. Then the drop responsibility stays with the caller. References, of course, can not be used for everything, but for our case it is sufficient. We mark the argument to \inline{print_value} as a reference using \inline{&}, and creating a reference from a value works the same.
\begin{lstlisting}[language=Rust, caption={Borrow using references to prevent a move}, label={lst:borrow}]
fn print_value(ptr: &Box<i32>) {
println!("{}", *ptr);
}
fn main() {
let mut ptr = Box::new(42);
print_value(&ptr);
*ptr += 1;
}
\end{lstlisting}
\autoref{lst:borrow} compiles without an error and does what we would expect. There are two kinds of references. We just looked at \emph{shared} or \emph{immutable references}. The other kind is \emph{unique} or \emph{mutable references}, and they are denoted with \inline{&mut}. The different kinds of references have a different semantics attached to them. While both are used to access values without taking ownership, there are specific rules for the creation and guarantees associated with each:
\item A shared reference to a value can always be created, as long as there is no unique reference to the same value. It is not possible to mutate the pointee through a shared reference (which is why it is also called immutable).
\item A unique reference can only be created if there is no other reference to the value at all and the referenced value is declared mutable. A unique reference can do everything the owner can, except dropping.
\end{itemize}
The part of the compiler that enforces these rules is the \emph{borrow checker}. The reasoning behind this is again resource safety, namely preventing \emph{unguarded mutable aliasing}. Having multiple readers of the same data doesn’t cause issues, as long as the data cannot be mutated. Mutating data is fine, as long as no one else can read and/or mutate the data at the same time. Using these two kinds of references enforces, at compile time, that we will always stay on the happy path. But there are some programs that are correct even though they violate these rules. We will be concerned with extending the borrow checker to accept more correct programs in later sections.
\subsubsection{The Owner of a Borrowed Value}
References have to agree to these rules, but the owner has to as well. While shared references exist, the owner may not mutate, e. g. \inline{let mut x = 42; let xref = &x; x += 1} is forbidden. Similarly, the owner can’t access a value at all as long as there is a unique reference around.
\textbf{This doesn’t not work, I have to think about this some more. I want something to throw Error E0507.}
In particular, a value may not be moved, as long as there is a reference to it because that would create a dangling pointer. This means that \inline{let y = &x; let z = *y;} doesn’t work.
%\begin{lstlisting}[language=Rust, caption={Moving out of borrowed value}]
% let x = 42;
% let xref = &x;
% let y = x;
%\end{lstlisting}
\subsubsection{Lexical Lifetimes and Lifetime Analysis}
The parts of the program in which a reference is valid is called its \emph{lifetime}. The borrow checker keeps track of all references’ lifetimes and their so-called \emph{provenances}, that is, who the original owner of the referenced value is. References are values like all others and so they are dropped at the end of the scope they are defined in. In other words: Their lifetime starts at their creation and ends at the end of the scope. This is called a \emph{lexical lifetime}. Therefore the code in \autoref{lst:lexical-lifetimes} does not compile.
\begin{lstlisting}[language=Rust, caption={Lexical Lifetimes prevent usage}, label={lst:lexical-lifetimes}]
let xref = &x; // `xref` is created
println!("{xref}");
x += 1; // error
} // `xref` is dropped
x += 1; // OK, no references exist anymore
\end{lstlisting}
The program in \autoref{lst:lexical-lifetimes} is rejected by our borrow checker since it registers a mutation to \inline{x} while a reference to it still exists. Only after the block ends, the borrow is returned to the owner and it can be used again. Similarly, we would not be able to create a unique reference while a shared one exists and vice versa.
But we can see that the program is correct since \inline{xref} is never accessed after \inline{x} is changed and we can save the code by introducing an additional scope.
\begin{lstlisting}[language=Rust, caption={More scopes make the code check}, label={lst:saving-lexical-lifetimes}]
let mut x = 0;
{
{
let xref = &x; // `xref` is created
println!("{xref}");
} // `xref` is dropped
x += 1;
}
x += 1;
\end{lstlisting}
This is tedious, though, and we would like the borrow checker to recognize patterns like this automatically. Luckily, Rust implements an improved borrow checker since version 1.31. Because it is not based on lexical scope for lifetime analysis anymore, these new lifetimes are called \acfi{NLL}. We will take a closer look at them in \autoref{subsec:non-lexical-lifetimes}.
\subsubsection{Access Guards}\todo{Put this in an info box?}
Sometimes we need access to a resource from multiple places at the same time, for example when sharing data between threads. For this, Rust provides the \inline{Mutex} container type. References to a mutex can be shared freely, but to change the value in the container, one has to acquire a lock, therefore making the access \emph{guarded}.\todo{Add example} While the mutex is locked, no other thread can access the data at all, a mutex lock is therefore similar to a \inline{&mut} but its guarantees are enforced at runtime. The \inline{RwLock} type can give out both read and write locks which have behavior analogous to \inline{&} and \inline{&mut}, respectively. There are more constructs for similar use cases in the standard library, like \inline{Arc} and \inline{Cow}.
These data structures are implemented using so-called \emph{raw pointers}. Raw pointers, like pointers in C, are not borrow checked and can therefore alias. The programmer has to check manually that no rules are violated and should provide a safe interface that hides the raw pointers from the users. \todo{Pointers to Strict Provenance, Miri, …?}
\subsubsection{Returning references and Borrow-through}
Functions can receive references as arguments, but they can also return references. One has to be a bit careful when doing this, though, since all resources created in the scope of a function are freed as soon as the function returns. Consider the following:
\begin{lstlisting}[language=Rust, caption={Try to return a reference}]
fn to_ref(number: i32) -> &i32 {
&number
}
\end{lstlisting}
This fails because \inline{number} is owned by \inline{to_ref} and is dropped as soon as the function returns. The reference would already be invalid when the caller gets access to it. But if the function takes a reference as an argument, it can pass another reference back to the caller. This is called a \emph{borrow-through} and looks like the following:
\begin{lstlisting}[language=Rust, caption={Borrow-through}]
fn to_ref(number: &i32) -> &i32 {
&number
}
\end{lstlisting}
Now Rust can couple both references to each other. The returned reference has the same provenance as the input one, and the borrow checker can check that the input reference’s lifetime is at least as long as the returned one.
\subsubsection{Lifetime Polymorphism}
Consider a function that takes two references and returns one reference itself. Imagine for example, we have two slices and want to create an iterator over all elements of both slices. Then we could try to write a function like in \autoref{lst:two-refs}. The \inline{impl Iterator<Item=&T>} is a so-called existential type. It just means that we don’t care about what type exactly it is as long as it is an iterator.
\begin{lstlisting}[language=Rust, caption={Iterator over two slices}, label={lst:two-refs}]
fn both(first: &[i32], second: &[i32])
-> impl Iterator<Item=&i32>
{
first.iter().chain(second.iter())
}
\end{lstlisting}
This does not work however.
\begin{lstlisting}
error[E0106]: missing lifetime specifier
\end{lstlisting}
The compiler is confused because it does not know which provenance and lifetime to assign to the returned references. We as programmers can see that the return value depends on both input references, so we can help Rust by providing lifetime hints.\footnote{Actually, the compiler could infer these lifetimes but it would rely on global program analysis to do so. It is also possible to infer function types, but Rust chose to always be explicit about the types and lifetimes you use and have them be explicit parts of the API of a function.}
\begin{lstlisting}[language=Rust, caption={Iterator over two slices}, label={lst:lifetime-parameters}]
fn both<'a>(first: &'a [i32], second: &'a [i32])
-> impl Iterator<Item=&'a i32>
{
first.iter().chain(second.iter())
}
\end{lstlisting}
\inline{'a} is a \emph{polymorphic lifetime parameter}. We tell Rust that the output relies on both inputs being valid for at least as long as itself. If the output relied on only one of the arguments, we would mark only one of them with a lifetime specifier. This would for example be the case if we want to search for an element in a slice and, if it exists, return a mutable reference to that element. We implement such a function in \autoref{lst:two-refs-one-dependent}.
\begin{lstlisting}[language=Rust, caption={The output lifetime depends only on one of the inputs}, label={lst:two-refs-one-dependent}]
fn find_mut<'a>(haystack: &'a mut [i32], needle: &i32)
-> Option<&'a mut i32>
{
haystack.iter_mut().find(|x| **x == *needle)
}
\end{lstlisting}
\subsection{Reference Conversions}
Sometimes, we have a reference to one type but need a reference to another, similar type. For example, a vector of some type \inline{Vec<T>} is conceptually the same as a slice of that same type \inline{[T]}, except that a \inline{Vec} can grow and shrink and a slice can not. This means that all functions which operate on slices should also work with vectors, and in fact there is a library method \inline{Vec::as_slice} that takes a reference to a vector and provides a reference to a slice. Similarly there is \inline{String::as_str} which transforms a \inline{&String} into a \inline{&str}.
Generally, there are many types that can act as a substitute for another. The common interface for this behavior is the \inline{AsRef} trait. There can be many implementations of this trait for a given type. for example, \inline{String} can stand in for \inline{str}, \inline{[u8]}, \inline{OsStr} and \inline{Path}. Every time a reference to one of these types are needed, we can use a \inline{String} instead, if we first call \inline{as_ref} on it:
\begin{lstlisting}[language=Rust, caption={Use \inline{String} in place of \inline{[u8]}}, label={lst:as_ref_bytes}]
fn needs_bytes(x: &[u8]) { /*...*/ }
// ...
let s = String::from("Hello Bytes");
needs_bytes(s.as_ref());
\end{lstlisting}
Always calling \inline{as_ref} is a bit cumbersome, especially if it is clear which type the target should be. For example, if we have a \inline{Box<T>} there is really only one reasonable type we want to get out of it, namely a \inline{&T}. The trait \inline{Deref} can be used for that. It specifies a single type which Rust automatically converts into if it is needed. For example, \inline{String} has \inline{str} as its \inline{Deref} target which allows us to omit the \inline{as_ref} in that case and leave the conversion implicit:
\begin{lstlisting}[language=Rust, caption={Use \inline{String} in place of \inline{[u8]}}]
fn needs_str(x: &str) { /*...*/ }
// ...
let s = String::from("Hello Bytes");
needs_bytes(s);
\end{lstlisting}
We couldn’t do the same in \autoref{lst:as_ref_bytes} because \inline{[u8]} is not \inline{Deref} target of \inline{String}.
\subsubsection{Borrow}
Sometimes we want to express an even stronger connection between two types. For example, a \inline{HashMap} needs to take ownership of its entries but we want to be able to do a lookup even if we only have a reference. But that requires that the owned and the borrowed value behave exactly the same. This is what the \inline{Borrow} trait signals. In particular, \inline{x.borrow() == y.borrow()} if and only if \inline{x == y} and \inline{x.hash() == x.borrow().hash()}.
\subsubsection{Mutable reference conversions}
All three of the aforementioned traits work for shared references only, but their variants \inline{AsMut}, \inline{DerefMut} and \inline{BorrowMut} all take and provide a unique reference.
\subsection{Non-Lexical Lifetimes}\label{subsec:non-lexical-lifetimes}
In \autoref{lst:lexical-lifetimes} we saw how lexical lifetimes stood in our way and we had to manually wrap a reference in an additional block just to make our correct program pass the borrow checker. This was because the lifetime of any value is bound to its scope, at the end of which it is dropped. \acl{NLL} is a different approach to lifetime analysis. A reference is live for as long as it may be used later. The borrow checker determines the points of the program in which the reference is live by building a \acfi{CFG}. All nodes of the \ac{CFG} that can be reached from the point of creation until the last use of the reference are where it is live. Consider the program in \autoref{lst:non-lexical-lifetimes}.
\begin{lstlisting}[language=Rust, caption={Simple \ac{NLL} example}, label={lst:non-lexical-lifetimes}]
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
let mut x = 0;
let xref = &x;
println!("{xref}");
x += 1;
\end{lstlisting}
Then the (simplified) \ac{CFG} looks something like \autoref{fig:cfg-non-lexical-lifetimes}. It is quite boring because the control flow is linear. The nodes with thick borders are where \inline{xref} is live. The first node is the one in which the reference is created and the second one is where it is last used. Because \inline{x} is not modified in that section of the \ac{CFG}, the borrow checker doesn’t complain.
\begin{figure}[!h]
\centering
\begin{tikzpicture}
\begin{scope}[every node/.style={draw, ellipse}]
\node (A) at (0,4.5) {\inline{let mut x = 0;}};
\node[ultra thick] (B) at (0,3) {\inline{let xref = &x;}};
\node[ultra thick] (C) at (0,1.5) {\inline{println!("\{xref\}");}};
\node (D) at (0,0) {\inline{x += 1;}};
\end{scope}
\path [->] (A) edge node[left] {} (B);
\path [->, ultra thick] (B) edge node[left] {} (C);
\path [->] (C) edge node[left] {} (D);
\end{tikzpicture}
\caption{Control-flow diagram for \autoref{lst:non-lexical-lifetimes}}
\label{fig:cfg-non-lexical-lifetimes}
\end{figure}
Consider a more complicated example that includes branching in \autoref{lst:non-lexical-lifetimes-branch}.
\begin{lstlisting}[language=Rust, caption={\ac{NLL} example with braching}, label={lst:non-lexical-lifetimes-branch}]
let mut x = 0;
let xref = &x;
if xref != &0 {
x += 1;
println!("xref equals {xref}");
} else {
println!("xref is 0");
}
x = 42;
\end{lstlisting}
Now the \ac{CFG} (\autoref{fig:cfg-non-lexical-lifetimes-branch}) splits up to accommodate both possible paths the program could take during execution. At the \inline{if}, the graph splits into two, but in the last node, both paths join up again. Here we can see that in the \inline{false} branch no problem occurs, but in the \inline{true} branch we try to modify \inline{x} even though \inline{xref} is used later in the same path. Modifying \inline{x} on the last line does not pose any problems since xref is not live anymore on any path that leads to this point.
\begin{figure}[!h]
\centering
\begin{tikzpicture}
\begin{scope}[every node/.style={draw, ellipse}]
\node (A) at (0,7.5) {\inline{let mut x = 0;}};
\node[ultra thick] (B) at (0,6) {\inline{let xref = &x;}};
\node[ultra thick] (C) at (0,4.5) {\inline{if xref != &0 }};
\node[ultra thick, draw=red] (D) at (-3,3) {\inline{x += 1;}};
\node[ultra thick] (E) at (-3,1.5) {\inline{println!("xref equals \{xref\}");}};
\node (G)[ultra thick] at (3,2.5) {\inline{println!("xref is 0");}};
\node (H) at (0,0) {\inline{x = 42;}};
\end{scope}
\path [->] (A) edge node[left] {} (B);
\path [->, ultra thick] (B) edge node[left] {} (C);
\path [->, ultra thick] (C) edge node[left] {\inline{true}} (D);
\path [->, ultra thick] (D) edge node[left] {} (E);
\path [->, ultra thick] (C) edge node[right] {\inline{false}} (G);
\path [->] (G) edge node[left] {} (H);
\path [->] (E) edge node[left] {} (H);
\end{tikzpicture}
\caption{Control-flow diagram for \autoref{lst:non-lexical-lifetimes-branch}}
\label{fig:cfg-non-lexical-lifetimes-branch}
\end{figure}
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
\subsection{Reborrows and Two-Phase Borrowing}
\ac{NLL} give us a lot more freedom when using references, but there are still programs that are clearly correct but don’t pass the borrow checker, especially if unique references are in play. It is not possible to create a new reference of some provenance as long as a unique reference with the same provenance is live. This means the code in \autoref{lst:conflicting-borrows} will not compile.
\begin{lstlisting}[language=Rust, caption={Cannot create a shared reference while a unique one exists}, label={lst:conflicting-borrows}]
let mut x = 1;
let ref1 = &mut x;
let ref2 = &x; // Error: Cannot borrow `x`
println!("{ref2}");
*ref1 = 2;
\end{lstlisting}
But since \inline{xs} is not live anymore when \inline{xu} is used, aliasing is never occurring, and if we hadn’t used a unique reference but modified the value directly, Rust would have been able to see this. Not being able to create references in this manner is a big thing. Consider the code in \autoref{lst:moved-borrow}. Here, we create a reference to an empty \inline{Vec} and then use it to \inline{push} a value. This moves the reference into the \inline{push} method so that it is dropped when the method returns. It is no longer valid to use it again in the next line since it has already been dropped. But in fact this code compiles because Rust implicitly inserts a \emph{reborrow} for us.
\begin{lstlisting}[language=Rust, caption={Move a borrow into a function}, label={lst:moved-borrow}]
let mut v = vec::new();
let vref = &mut v;
vref.push(1);
vref.last(); // Error: Use of moved value `vref`
\end{lstlisting}
\subsubsection{Reborrows}
Let’s get back to the example in \autoref{lst:conflicting-borrows} for now. The compiler complains because we try to create two conflicting references with the same provenance. But we can tell the compiler to temporarily deactivate a reference by borrowing \emph{through this reference} directly. This is done in \autoref{lst:simple-reborrow}.
\begin{lstlisting}[language=Rust, caption={Reborrow through a unique reference}, label={lst:simple-reborrow}]
let mut x = 1;
let ref1 = &mut x;
let ref2 = & *ref1; // Reborrow
println!("{ref2}");
*ref1 = 2;
\end{lstlisting}
Now we can create and use the \inline{ref2} as long as it is not live anymore when we use \inline{ref1} again. If the last two lines were swapped, the borrow checker would correctly find the interleaved use and throw an error. It is possible to nest reborrows, as long as they are never used interleaved.
\begin{enumerate}
\item What is a reborrow?
\item Example: reborrows are correct but rejected
\item Solution: Allow for reborrows
\item Using shared references after creating a unique one is correct but rejected
\item Example
\item Solution: Two-Phase Borrowing
\item Note: TPB is not yet implemented
\end{enumerate}
\subsection{Loans and Regions}
\begin{enumerate}
\item What are loans and regions?
\item regions as an alternative to lifetimes
\item regions are more powerful
\item Piggyback on the talk by \href{https://www.youtube.com/watch?v=_agDeiWek8w}{Niko Matsakis}
\item Note: Polonius will be a new borrow checker using regions but it is unstable
\item Note: concrete memory regions à la Cyclone vs abstract regions à la Polonius
\end{enumerate}
\section{Contracts and Refinement Types}
\subsection{Contracts}
\begin{enumerate}
\item Preconditions, Postconditions, Invariants
\item Hoare Logic
\item Contracts are a Runtime Check
\item Are there first-class contracts?
\item Higher-order contracts (\href{https://www2.ccs.neu.edu/racket/pubs/icfp2002-ff.pdf}{Findler, Felleisen})
\end{enumerate}
\subsection{Refinement Types}
\begin{enumerate}
\item Independent Refinement Types
\item Dependent Refinement Types/Refined Function Types
\item Refinement Types are “first-class”
\end{enumerate}
\subsection{Liquid Types and SMT Solving}
\begin{enumerate}
\item Refinement Types are not decidable
\item SAT is decidable
\item SMT is decidable
\item Liquid Types = $\{ T : \text{RefinementType } | \text{ decidable } T \}$
\end{enumerate}
\subsection{Hybrid Type Checking}
\begin{enumerate}
\item Idea: Combine Contracts and Liquid Types
\item Sprinkle in casts everywhere an assertion is made
\item If the compiler can prove that this is ok or not ok, remove the cast (and potentially throw a compiler error)
\item All other casts are dynamic and lead to a panic if they fail.
\item In Rust, \inline{Result} and \inline{?} could be used instead. Problem: The error must be handled somewhere.
\end{enumerate}
\section{Other Extensions to a Type System}
\subsection{Totality and Termination Metrics}
\begin{enumerate}
\item Some functions can panic or loop indefinitely
\item In Rust, \inline{!} as return type helps (such a function does definitely not terminate)
\item There is no “may not terminate”, though, so you have to rely on the documentation
\item You can prove that a loop/recursion finishes by termination metrics
\item Panics can technically occur in every function (every function call needs memory space for a stack frame)
\item But you can prove that there are no “high-level” panics in any execution path within a function
\end{enumerate}
\subsection{(Other) Substructural Types}
\begin{enumerate}
\item What are structural rules? Exchange, Weakening, Contraction
\item Move semantics are affine types
\item Clone and Copy is weakening
\item \inline{#[must_use]} and relevant types
\item The borrow stack is ordered types
\item Uniqueness types: There is only one reference (in contrast to linear types: no more references can be created)
\todo[inline]{Proof objects? Compile-time only linear values}
\subsection{Purity and (algebraic) Effects}
\begin{enumerate}
\item Effects
\item Handling (Example: Exceptions)
\item Algebraic Effects
\item Pure functions don’t have unhandled effects
\item What counts as an effect (IO, allocation, computation)
\item non-totality is an effect, but do termination metrics fit in?
\item Weakening and Contraction are effects, but what about borrowing?
\item Refinements $\cap$ Effects