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

Purity #5412

Merged
merged 3 commits into from
Mar 19, 2013
Merged

Purity #5412

Show file tree
Hide file tree
Changes from all commits
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
45 changes: 4 additions & 41 deletions doc/rust.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ false fn for
if impl
let loop
match mod mut
priv pub pure
priv pub
ref return
self static struct super
true trait type
Expand Down Expand Up @@ -936,7 +936,6 @@ Specifically, the following operations are considered unsafe:

- Dereferencing a [raw pointer](#pointer-types).
- Casting a [raw pointer](#pointer-types) to a safe pointer type.
- Breaking the [purity-checking rules](#pure-functions) in a `pure` function.
- Calling an unsafe function.

##### Unsafe blocks
Expand All @@ -946,42 +945,6 @@ This facility exists because the static semantics of Rust are a necessary approx
When a programmer has sufficient conviction that a sequence of unsafe operations is actually safe, they can encapsulate that sequence (taken as a whole) within an `unsafe` block. The compiler will consider uses of such code "safe", to the surrounding context.


#### Pure functions

A pure function declaration is identical to a function declaration, except that
it is declared with the additional keyword `pure`. In addition, the typechecker
checks the body of a pure function with a restricted set of typechecking rules.
A pure function may only modify data owned by its own stack frame.
So, a pure function may modify a local variable allocated on the stack, but not a mutable reference that it takes as an argument.
A pure function may only call other pure functions, not general functions.

An example of a pure function:

~~~~
pure fn lt_42(x: int) -> bool {
return (x < 42);
}
~~~~

Pure functions may call other pure functions:

~~~~{.xfail-test}
pure fn pure_length<T>(ls: List<T>) -> uint { ... }

pure fn nonempty_list<T>(ls: List<T>) -> bool { pure_length(ls) > 0u }
~~~~

These purity-checking rules approximate the concept of referential transparency:
that a call-expression could be rewritten with the literal-expression of its return value, without changing the meaning of the program.
Since they are an approximation, sometimes these rules are *too* restrictive.
Rust allows programmers to violate these rules using [`unsafe` blocks](#unsafe-blocks), which we already saw.
As with any `unsafe` block, those that violate static purity carry transfer the burden of safety-proof from the compiler to the programmer.
Programmers should exercise caution when breaking such rules.

For more details on purity, see [the borrowed pointer tutorial][borrow].

[borrow]: tutorial-borrowed-ptr.html

#### Diverging functions

A special kind of function can be declared with a `!` character where the
Expand Down Expand Up @@ -1246,10 +1209,10 @@ For example:

~~~~
trait Num {
static pure fn from_int(n: int) -> Self;
static fn from_int(n: int) -> Self;
}
impl Num for float {
static pure fn from_int(n: int) -> float { n as float }
static fn from_int(n: int) -> float { n as float }
}
let x: float = Num::from_int(42);
~~~~
Expand Down Expand Up @@ -2643,7 +2606,7 @@ Raw pointers (`*`)
### Function types

The function type-constructor `fn` forms new function types. A function type
consists of a set of function-type modifiers (`pure`, `unsafe`, `extern`, etc.),
consists of a set of function-type modifiers (`unsafe`, `extern`, etc.),
a sequence of input slots and an output slot.

An example of a `fn` type:
Expand Down
34 changes: 17 additions & 17 deletions doc/tutorial-borrowed-ptr.md
Original file line number Diff line number Diff line change
Expand Up @@ -486,12 +486,12 @@ For example, we could write a subroutine like this:

~~~
struct Point {x: float, y: float}
fn get_x(p: &r/Point) -> &r/float { &p.x }
fn get_x(p: &'r Point) -> &'r float { &p.x }
~~~

Here, the function `get_x()` returns a pointer into the structure it
was given. The type of the parameter (`&r/Point`) and return type
(`&r/float`) both use a new syntactic form that we have not seen so
was given. The type of the parameter (`&'r Point`) and return type
(`&'r float`) both use a new syntactic form that we have not seen so
far. Here the identifier `r` names the lifetime of the pointer
explicitly. So in effect, this function declares that it takes a
pointer with lifetime `r` and returns a pointer with that same
Expand Down Expand Up @@ -572,8 +572,8 @@ function:
# Rectangle(Point, Size) // upper-left, dimensions
# }
# fn compute_area(shape: &Shape) -> float { 0f }
fn select<T>(shape: &r/Shape, threshold: float,
a: &r/T, b: &r/T) -> &r/T {
fn select<T>(shape: &'r Shape, threshold: float,
a: &'r T, b: &'r T) -> &'r T {
if compute_area(shape) > threshold {a} else {b}
}
~~~
Expand All @@ -593,17 +593,17 @@ example:
# }
# fn compute_area(shape: &Shape) -> float { 0f }
# fn select<T>(shape: &Shape, threshold: float,
# a: &r/T, b: &r/T) -> &r/T {
# a: &'r T, b: &'r T) -> &'r T {
# if compute_area(shape) > threshold {a} else {b}
# }
// -+ r
fn select_based_on_unit_circle<T>( // |-+ B
threshold: float, a: &r/T, b: &r/T) -> &r/T { // | |
// | |
let shape = Circle(Point {x: 0., y: 0.}, 1.); // | |
select(&shape, threshold, a, b) // | |
} // |-+
// -+
// -+ r
fn select_based_on_unit_circle<T>( // |-+ B
threshold: float, a: &'r T, b: &'r T) -> &'r T { // | |
// | |
let shape = Circle(Point {x: 0., y: 0.}, 1.); // | |
select(&shape, threshold, a, b) // | |
} // |-+
// -+
~~~

In this call to `select()`, the lifetime of the first parameter shape
Expand All @@ -629,8 +629,8 @@ returned. Here is how the new `select()` might look:
# Rectangle(Point, Size) // upper-left, dimensions
# }
# fn compute_area(shape: &Shape) -> float { 0f }
fn select<T>(shape: &tmp/Shape, threshold: float,
a: &r/T, b: &r/T) -> &r/T {
fn select<T>(shape: &'tmp Shape, threshold: float,
a: &'r T, b: &'r T) -> &'r T {
if compute_area(shape) > threshold {a} else {b}
}
~~~
Expand All @@ -649,7 +649,7 @@ concise to just omit the named lifetime for `shape` altogether:
# }
# fn compute_area(shape: &Shape) -> float { 0f }
fn select<T>(shape: &Shape, threshold: float,
a: &r/T, b: &r/T) -> &r/T {
a: &'r T, b: &'r T) -> &'r T {
if compute_area(shape) > threshold {a} else {b}
}
~~~
Expand Down
4 changes: 2 additions & 2 deletions src/libcore/at_vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,9 @@ pub mod traits {
use kinds::Copy;
use ops::Add;

impl<T:Copy> Add<&self/[const T],@[T]> for @[T] {
impl<T:Copy> Add<&'self [const T],@[T]> for @[T] {
#[inline(always)]
pure fn add(&self, rhs: & &self/[const T]) -> @[T] {
pure fn add(&self, rhs: & &'self [const T]) -> @[T] {
append(*self, (*rhs))
}
}
Expand Down
12 changes: 6 additions & 6 deletions src/libcore/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,17 @@ pub unsafe fn transmute<L, G>(thing: L) -> G {

/// Coerce an immutable reference to be mutable.
#[inline(always)]
pub unsafe fn transmute_mut<T>(ptr: &a/T) -> &a/mut T { transmute(ptr) }
pub unsafe fn transmute_mut<T>(ptr: &'a T) -> &'a mut T { transmute(ptr) }

/// Coerce a mutable reference to be immutable.
#[inline(always)]
pub unsafe fn transmute_immut<T>(ptr: &a/mut T) -> &a/T {
pub unsafe fn transmute_immut<T>(ptr: &'a mut T) -> &'a T {
transmute(ptr)
}

/// Coerce a borrowed pointer to have an arbitrary associated region.
#[inline(always)]
pub unsafe fn transmute_region<T>(ptr: &a/T) -> &b/T { transmute(ptr) }
pub unsafe fn transmute_region<T>(ptr: &'a T) -> &'b T { transmute(ptr) }

/// Coerce an immutable reference to be mutable.
#[inline(always)]
Expand All @@ -85,19 +85,19 @@ pub unsafe fn transmute_immut_unsafe<T>(ptr: *const T) -> *T {

/// Coerce a borrowed mutable pointer to have an arbitrary associated region.
#[inline(always)]
pub unsafe fn transmute_mut_region<T>(ptr: &a/mut T) -> &b/mut T {
pub unsafe fn transmute_mut_region<T>(ptr: &'a mut T) -> &'b mut T {
transmute(ptr)
}

/// Transforms lifetime of the second pointer to match the first.
#[inline(always)]
pub unsafe fn copy_lifetime<S,T>(_ptr: &a/S, ptr: &T) -> &a/T {
pub unsafe fn copy_lifetime<S,T>(_ptr: &'a S, ptr: &T) -> &'a T {
transmute_region(ptr)
}

/// Transforms lifetime of the second pointer to match the first.
#[inline(always)]
pub unsafe fn copy_lifetime_vec<S,T>(_ptr: &a/[S], ptr: &T) -> &a/T {
pub unsafe fn copy_lifetime_vec<S,T>(_ptr: &'a [S], ptr: &T) -> &'a T {
transmute_region(ptr)
}

Expand Down
13 changes: 12 additions & 1 deletion src/libcore/cell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,29 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use cast::transmute;
use option;
use prelude::*;

/// A dynamic, mutable location.
///
/// Similar to a mutable option type, but friendlier.

#[deriving_eq]
pub struct Cell<T> {
mut value: Option<T>
}

impl<T:cmp::Eq> cmp::Eq for Cell<T> {
pure fn eq(&self, other: &Cell<T>) -> bool {
unsafe {
let frozen_self: &Option<T> = transmute(&mut self.value);
let frozen_other: &Option<T> = transmute(&mut other.value);
frozen_self == frozen_other
}
}
pure fn ne(&self, other: &Cell<T>) -> bool { !self.eq(other) }
}

/// Creates a new full cell with the given value.
pub fn Cell<T>(value: T) -> Cell<T> {
Cell { value: Some(value) }
Expand Down
4 changes: 2 additions & 2 deletions src/libcore/cleanup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ use cast::transmute;
* NB: These must match the representation in the C++ runtime.
*/

type DropGlue = &self/fn(**TypeDesc, *c_void);
type FreeGlue = &self/fn(**TypeDesc, *c_void);
type DropGlue = &'self fn(**TypeDesc, *c_void);
type FreeGlue = &'self fn(**TypeDesc, *c_void);

type TaskID = uintptr_t;

Expand Down
10 changes: 8 additions & 2 deletions src/libcore/comm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use cast;
use either::{Either, Left, Right};
use kinds::Owned;
use option;
use option::{Option, Some, None, unwrap};
use uint;
use unstable;
use vec;

Expand Down Expand Up @@ -283,8 +285,12 @@ impl<T: Owned> Peekable<T> for PortSet<T> {
pure fn port_set_peek<T:Owned>(self: &PortSet<T>) -> bool {
// It'd be nice to use self.port.each, but that version isn't
// pure.
for vec::each(self.ports) |p| {
if p.peek() { return true }
for uint::range(0, vec::uniq_len(&const self.ports)) |i| {
// XXX: Botch pending demuting.
unsafe {
let port: &Port<T> = cast::transmute(&mut self.ports[i]);
if port.peek() { return true }
}
}
false
}
Expand Down
10 changes: 5 additions & 5 deletions src/libcore/condition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ pub struct Handler<T, U> {
}

pub struct Condition<T, U> {
name: &static/str,
name: &'static str,
key: task::local_data::LocalDataKey/&self<Handler<T, U>>
}

pub impl<T, U> Condition/&self<T, U> {
fn trap(&self, h: &self/fn(T) -> U) -> Trap/&self<T, U> {
fn trap(&self, h: &'self fn(T) -> U) -> Trap/&self<T, U> {
unsafe {
let p : *RustClosure = ::cast::transmute(&h);
let prev = task::local_data::local_data_get(self.key);
Expand Down Expand Up @@ -65,12 +65,12 @@ pub impl<T, U> Condition/&self<T, U> {
}

struct Trap<T, U> {
cond: &self/Condition/&self<T, U>,
cond: &'self Condition/&self<T, U>,
handler: @Handler<T, U>
}

pub impl<T, U> Trap/&self<T, U> {
fn in<V>(&self, inner: &self/fn() -> V) -> V {
fn in<V>(&self, inner: &'self fn() -> V) -> V {
unsafe {
let _g = Guard { cond: self.cond };
debug!("Trap: pushing handler to TLS");
Expand All @@ -81,7 +81,7 @@ pub impl<T, U> Trap/&self<T, U> {
}

struct Guard<T, U> {
cond: &self/Condition/&self<T, U>
cond: &'self Condition/&self<T, U>
}

impl<T, U> Drop for Guard/&self<T, U> {
Expand Down
6 changes: 3 additions & 3 deletions src/libcore/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ use option::Option;

pub trait Container {
/// Return the number of elements in the container
pure fn len(&self) -> uint;
pure fn len(&const self) -> uint;

/// Return true if the container contains no elements
pure fn is_empty(&self) -> bool;
pure fn is_empty(&const self) -> bool;
}

pub trait Mutable: Container {
Expand All @@ -39,7 +39,7 @@ pub trait Map<K, V>: Mutable {
fn mutate_values(&mut self, f: &fn(&K, &mut V) -> bool);

/// Return the value corresponding to the key in the map
pure fn find(&self, key: &K) -> Option<&self/V>;
pure fn find(&self, key: &K) -> Option<&'self V>;

/// Insert a key-value pair into the map. An existing value for a
/// key is replaced by the new value. Return true if the key did
Expand Down
2 changes: 1 addition & 1 deletion src/libcore/gc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ unsafe fn is_safe_point(pc: *Word) -> Option<SafePoint> {
return None;
}

type Visitor = &self/fn(root: **Word, tydesc: *Word) -> bool;
type Visitor = &'self fn(root: **Word, tydesc: *Word) -> bool;

// Walks the list of roots for the given safe point, and calls visitor
// on each root.
Expand Down
Loading