Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add more and better Rust examples #10

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Tags: main, pointers
/** Description:

This example corrects the broken 03-lifetimes-broken.c. Here `r`
is inside the scope of `x` so things work out. However the
lifetime usage is not very interesting.

*/

/* Rust code:

fn main() {
let x = 42; // 'x' lives throughout the outer scope
let r = borrow(&x); // 'r' now correctly borrows 'x' and can be used as long as 'x' is alive
println!("The number is: {}", r); // Safe to use 'r' here
}

fn borrow<'a>(input: &'a i32) -> &'a i32 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be nice to make this be the same as the C code (i.e., write compare in Rust)

input
}

*/

/* C + CN translation */


//#include <stdio.h>
// Function to "borrow" an integer pointer
int* compare(int* p, int* bound)
/*@ requires take vp0 = Block<int>(p);
take vb0 = Block<int>(bound);
ensures take vp1 = Block<int>(p);
take vb1 = Block<int>(bound);
return == p;
@*/
{
if (p==bound){
//printf("yes");
// Do something
}
return p; // Return the input pointer
}

int main() {
int x[] = {42, 0}; // 'x' lives throughout the outer scope
int* r = compare(x, x+1); // 'r' now correctly points to 'x' and can be used as long as 'x' is alive
int ret = *r; // Safe to use 'r' here, as 'x' is still in scope


//printf("%d", ret);
return ret;
}
79 changes: 79 additions & 0 deletions src/example-archive/Rust/broken/error-crash/00012-move.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Tags: Rust, strings

// Tags: main, pointers
/** Description:

Rust's ownership system is governed by three main rules:

1. Each value in Rust has an owner.
2. There can only be one owner at a time.
3. When the owner goes out of scope, the value will be dropped.

In Rust, the code below would fail to typecheck because the
assignment from `s1` to `s2` transfers the ownership of the
pointer from `s1` to `s2`. Consequently, `s1` becomes unusable
while `s2` is in scope.


In contrast, CN treats ownership not as an attribute of an entity,
but as a transient aspect of the program's execution—a 'ghost
state' that does not impact performance. As a result, there are no
ownership transfers in CN, allowing verification of the example
without issues.


Note that in Rust, `use_string` accepts a slice, which is
essentially a 'fat pointer' comprising a pointer, a length, and a
capacity. To achieve a similar functionality in C, we must
explicitly pass the length `len` as an argument. More details
about slices can be found in `slices.c`."

*/

/* Rust code:

fn move(){
let x = 42;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to have a string here? Probably better given the name use_string and also numbers are Copy so the assignment might actually work with a number.

let s1 = &x;
let s2 = s1;
use_string(s1);
}
*/


Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is not the point here, but would this be better with Block rather than Owned, as allocating and deallocating a string shouldn't really require it to be initialized?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I was trying to mimic what Rust does and reduce complexity. The comment for the function explains that it creates and initializes the string, but perhaps I should change the name as well to capture that.

// Allocates and populates a string of size `len`.
extern char *createString (unsigned int len);
/*@ spec createString(pointer p, u32 len);
requires true;
ensures take vp1 = each(u32 i; i < len) { Owned<char>( array_shift<char>(return, i)) };
@*/

extern char *freeString (char* p, unsigned int len);
/*@ spec freeString(pointer p, u32 len);
requires take vp1 = each(u32 i; i < len) { Owned<char>( array_shift<char>(p, i)) };
ensures true;
@*/

// Uses a string, e.g. IO
extern void use_string(char* p, unsigned int len);
/*@ spec use_string(pointer p, u32 len);
requires take vp0 = each(u32 i; i < len) { Owned<char>( array_shift<char>(p, i)) };
ensures take vp1 = each(u32 i; i < len) { Owned<char>( array_shift<char>(p, i)) };
@*/

void move()
{
unsigned int len = 13;
char* s1 = createString(len);
char* s2 = s1;

use_string(s1, len); // Without move, we can still use `s1`
use_string(s2, len); // We can call use_string again on `s2`, as long as its
// spec returns the ownership.

freeString(s2, len);
}

int main(){
move();
}
87 changes: 87 additions & 0 deletions src/example-archive/Rust/broken/error-crash/00013-slices.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Tags: Rust, strings

// Tags: main, pointers
/** Description:

Rust Slices provide a safe view into an array and manage both
bounds and borrowing rules automatically. This system prevents
common runtime errors such as buffer overflows and illegal memory
access. Rust's borrow checker enforces access rules that ensure
the data is neither modified unexpectedly nor accessed beyond its
life, thus maintaining safety across the function's execution.

On the other hand, the CN uses iterated resources to reason about
array ownershiop. For example, the precondition for `has_zero` require that

`take vp0 = each(u32 i; i < len) { Owned<int>( array_shift<int>(p, i)) };`

This iterated resource corresponds to Rust's slices and states
that all the elements, from index 0 to `len`, are owned.

Often times resources have to be manually extracted from an
iterated resource by using the `extract` hint. In the example,
this is used before accessing `p[i]` to demonstrate that the
execution has the ownership required to read the value.

*/

/* Rust code:

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it is better to do this example with a slice containing elements of a type that is not Copy, as it would make it more explicit what's happening to the ownership. In particular, you wouldn't use the pattern in the for loop, so you'd have for item in p, and then at the use site you'd have to do a clone (i.e., item.clone()).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you say more about this, I'm not sure I understand.

fn has_zero(p: &[i32]) -> bool {
// Iterate over the slice and check if any element is zero
for &item in p {
if item == 0 { // Check for zero instead of space (correcting the apparent mistake in the original C code)
return true;
}
}
false
}

fn use_slices() -> bool {
let fibs: [i32; 10] = [1, 1, 2, 3, 5, 8, 13, 0, 21, 34];
let s1 = has_zero(&fibs); // Pass a slice of the array

s1
}

fn main() {
let ret_ = use_slices();
println!("Contains zero: {}", ret_);
}

*/

#include <stdbool.h> // Include for `bool` type


Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are checking for 0, not space (the comment is wrong)

// Check if there is a space within the first `len` characters of the string `s`
bool has_zero(const int *p, unsigned int len)
/*@ requires take vp0 = each(u32 i; i < len) { Owned<int>( array_shift<int>(p, i)) };
ensures take vp2 = each(u32 i; i < len) { Owned<int>( array_shift<int>(p, i)) };
@*/
{
for (unsigned int i = 0; i < len; i++)
/*@ inv take vp1 = each(u32 j; j < len) { Owned<int>( array_shift<int>(p, j)) };
{p} unchanged;
@*/
{
/*@ extract Owned<unsigned int>, i; @*/
if (p[i] == 0) {
return true; // Return true if a space is found
}
}
return false; // Return false if no space is found
}


bool use_slices()
{
int fibs[10] = {1,1,2,3,5,8,13,0,21,34};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment seems wrong, s1 is returned?

bool s1 = has_zero(fibs,10); // `s1_` is unused

return s1;
}

int main(){
bool ret_ = use_slices();
}
16 changes: 16 additions & 0 deletions src/example-archive/Rust/broken/error-crash/00014-exception.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Tags: arrays

// Tags: main, pointers
/** Description:

Exception triggered by a spec creating arrays.

*/


// Allocates and populates an array of size `len`.
extern int *createArray (unsigned int len);
/*@ spec createArray(pointer p, u32 len);
requires true;
ensures take vp1 = each(u32 i; i < len) { Owned<int>( array_shift<int>(return, i)) };
@*/
78 changes: 78 additions & 0 deletions src/example-archive/Rust/working/00009-read_write.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Tags: Rust, strings, main, custom malloc

// Tags: main, pointers
/** Description:

CN and Rust have different mutability models. This example
highlights one particular difference:

In Rust there are mutable (i.e. read-write) reference `&mut p`
read-only reference `&p`. Safe Rust doens't allow uninitialized
memory, so there is no write-only permission.

In CN `Block<T>(p)` represents uninitialized memory and is
write-only. The `Owned<T>(p)` permission is read-write. There is
no read-only permission.

A `Owned<T>(p)` permission is strictly stronger than a
`Block<T>(p)` and can always be lowered to satisfy the former.
After the write `*s1 = 42;` the resource becomes initialized, so
the ownership is increased to `Owned<T>(p)`, but the stronger
permission is enough to satisfy the the spec of `freeInt` wich
requires a `Block<T>(p)`.

*/

/* Rust code:

fn read_write() -> i32 {
let mut s1 = Box::new(0); // Allocate memory on the heap and
// it is immidiately initialized
let res: i32;

*s1 = 42;

res = *s1;

mem::drop(s1); // Free memory

res
}

*/


// Allocates an uninitialized integer in head.
extern int *mallocInt ();
/*@ spec mallocInt();
requires true;
ensures take v = Block<int>(return);
@*/

// Deallocates an integer.
extern void freeInt (int *p);
/*@ spec freeInt(pointer p);
requires take v = Block<int>(p);
ensures true;
@*/

int read_write()
{
int* s1 = mallocInt();
int res;

// res = *s1; // Fail, becasue s1 is not initilaized

*s1 = 42; // Can write thanks to `Block<T>(p)` permission.
// After writting we have `Owned<T>(p)` permission.

res = *s1; // Succedes, now s1 is initialized

freeInt(s1);

return res;
}

int main(){
read_write(); // ignore return
}
Loading