Skip to content
Snippets Groups Projects
Commit 8d6203bc authored by Jasper Clemens Gräflich's avatar Jasper Clemens Gräflich
Browse files

Continue 1.2.1 and fix package loading

parent 840856ea
No related branches found
No related tags found
No related merge requests found
Pipeline #16465 passed
% Needed for correct loading order
\RequirePackage{amsmath}
%%%
% Font and Language
%%%
......@@ -36,6 +39,9 @@
% that `~' is sufficiently rare in Rust to never encounter it.
\newcommand{{\inline}}[2][rust]{{{\mintinline{#1}~#2~}}}
% Autoref for minted listings
\providecommand*{\listingautorefname}{Listing}
% Caption formatting; I can’t get it to align the whole
% caption to the left, only the paragraphs within the caption.
\usepackage[font=footnotesize]{caption}
......
......@@ -15,10 +15,10 @@ There are mainly three kinds of contract:
\begin{enumerate}
\item \emph{Preconditions} are checked at the beginning of some piece of code,
\item \emph{Postconditions} are checked after some piece of code has run, and
\item \emph{Invariants} must always hold for some piece of data. An invariant on a piece of data is equivalent to checking the invariant as both a pre- and postcondition for each computation involving this data.
\item \emph{Invariants} must always hold for some piece of data. An invariant on a piece of data is equivalent to checking the invariant as both a pre- and postcondition for each computation involving this data.\todo{Not really, because the invariant may be violated during the runtime of a method as long as it is not visible outside of the method (which it could be in a parallel shared mutable access situation). In D, invariants are checked after pre- and before postconditions of all public methods.}
\end{enumerate}
We can model a bank transfer system as an example: A bank account has a balance with the invariant that it may never below line of credit. One can withdraw money from an account, but only if the account is not overdrawn. When transferring money from one account to another, there is a precondition on the first account to have enough money and a postcondition that the balances of the two accounts are updated. An implementation in Rust without any contract checking is given in \autoref{lst:banking-nocheck}.
We can model a bank transfer system as an example: A bank account has a balance with the invariant that it may never below line of credit. One can withdraw money from an account, but only if the account is not overdrawn. When transferring money from one account to another, there is a precondition on the first account to have enough money and a postcondition that the total amount of money hasn’t changed. Both the withdrawal and the transfer must use nonnegative \inline{amount}. An implementation in Rust without any contract checking is given in \autoref{lst:banking-nocheck}.
\begin{listing}[H]
\begin{minted}{rust}
......@@ -57,7 +57,7 @@ This version of the code is dangerous because no checks are present whatsoever.
\begin{minted}{rust}
// Implementation of `std::error::Error` is omitted.
struct BankingError;
struct Account {
line_of_credit: i32,
balance: i32,
......@@ -107,33 +107,61 @@ This version of the code is dangerous because no checks are present whatsoever.
\label{lst:banking-results}
\end{listing}
This ensures that the invariant for \inline{Account.balance} is not broken and the instead consumer of the API has to manually check if a method call was successful. Also, the possibility of failure is a visible part of the API now. An additional advantage is that this library will not panic if a condition is violated. But it incurs a lot of boilerplate (much of it lies in the implementation of \inline{std::error::Error} for \inline{BankingError} which is omitted here) and repeated code. Also note that there is an additional hidden condition in that the programmer must always keep in mind that \inline{line_of_credit <= 0}.\todo{Reformulate}
This ensures that the invariant for \inline{balance} is not broken and the instead consumer of the API has to manually check if a method call was successful. Also, the possibility of failure is a visible part of the API now. An additional advantage is that this library will not panic if a condition is violated. But it incurs a lot of boilerplate (much of it lies in the implementation of \inline{std::error::Error} for \inline{BankingError} which is omitted here) and repeated code. Also note that there is an additional hidden condition in that the programmer must always keep in mind that \inline{line_of_credit <= 0}.\todo{Reformulate}
\subsubsection{Assertions} While using \inline{Result} and \inline{Option} types is safe and explicit, it comes with a considerable overhead for both the user and the maintainer of a library. Often this overhead is undesirable and the program is allowed to just crash on failure. Assertions can be used for that. \inline{assert!(foo)} checks if \inline{foo} is \inline{true} and panics if not.
\subsubsection{Assertions} An assertion checks a condition and panics if the condition is not met. For example, an (a bit pointless) square root function could look like the following:
\begin{listing}[H]
\begin{minted}{rust}
fn sqrt(n: f64) -> f64 {
assert!(n >= 0.0);
let result = n.sqrt();
assert!(result >= 0.0);
result
struct Account {
line_of_credit: i32,
balance: i32,
}
impl Account {
fn change_line_of_credit(&mut self, new: i32) {
assert!(balance >= new);
self.line_of_credit = new;
}
fn withdraw(&mut self, amount: i32) -> i32 {
assert!(self.balance - amount >= self.line_of_credit);
assert!(amount >= 0);
self.balance -= amount;
amount
}
fn transfer(
&mut self,
&mut other: Self,
amount: i32,
) {
assert!(self.balance - amount >= self.line_of_credit);
assert!(amount >= 0);
self.balance -= amount;
other.balance += amount;
}
}
\end{minted}
\caption{Enforcing a contract using assertions}
\label{lst:assertion}
\label{lst:banking-assert}
\end{listing}
The first \inline{assert} assures that \inline{n} conforms to the precondition of being non-negative, the second \inline{assert} checks that the returned value conforms to the postcondition, which is the same in this case.
The assertions in \autoref{lst:banking-assert} again ensure the invariant on \inline{balance}. But now the failure is not part of the API and must be documented separately. Also, there are still more conditions which are not checked here. Checking those would be very verbose.
Also, it might be desirable to only use assertions during testing but not in production code. For this use case, Rust provides debug assertions that are not compiled in release mode.
\subsubsection{Integrated Contract Programming}
Programming languages like Eiffel and D have native support for contract programming. The \href{contracts}{https://gitlab.com/karroffel/contracts} crate brings design by contract to Rust.
Programming languages like Eiffel and D have native support for contract programming. The \href{https://gitlab.com/karroffel/contracts}{\inline{contracts}} and \href{https://github.com/erichdongubler/adhesion-rs}{adhesion} crates bring restricted versions of design by contract to Rust.
\todo[inline]{Example bank transfer: A bank account may never have a balance below lino of credit, a transfer precondition is that there is enough money in the first account, a postcondition is that the balancs are updated. Use Eiffel, D, or Rust/contracts?}
\subsubsection{loop invariants/conditions}
\todo[inline]{loop invariants/conditions}
\subsubsection{Higher-order contracts}
\todo[inline]{Higher-order contracts: \href{https://www2.ccs.neu.edu/racket/pubs/icfp2002-ff.pdf}{Findler, Felleisen}}
\href{https://www2.ccs.neu.edu/racket/pubs/icfp2002-ff.pdf}{Findler, Felleisen}
\end{document}
\ No newline at end of file
import std;
struct Account {
int line_of_credit;
int balance;
invariant {
assert(line_of_credit <= 0);
assert(balance >= line_of_credit);
}
void change_line_of_credit(int new_val)
in (new_val <= 0)
in (new_val <= balance)
{
line_of_credit = new_val;
}
// `in (amount ...)` is not needed because
// of the invariants
int withdraw(int amount)
in (amount >= 0)
{
balance -= amount;
return amount;
}
void transfer(Account other, int amount)
in (amount >= 0)
out(; balance + other.balance)
{
balance -= amount;
other.balance += amount;
}
}
void main()
{
writeln("Hello D");
}
\ No newline at end of file
No preview for this file type
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment