Skip to content

Commit

Permalink
librustc: Make the compiler ignore purity.
Browse files Browse the repository at this point in the history
For bootstrapping purposes, this commit does not remove all uses of
the keyword "pure" -- doing so would cause the compiler to no longer
bootstrap due to some syntax extensions ("deriving" in particular).
Instead, it makes the compiler ignore "pure". Post-snapshot, we can
remove "pure" from the language.

There are quite a few (~100) borrow check errors that were essentially
all the result of mutable fields or partial borrows of `@mut`. Per
discussions with Niko I think we want to allow partial borrows of
`@mut` but detect obvious footguns. We should also improve the error
message when `@mut` is erroneously reborrowed.
  • Loading branch information
pcwalton committed Mar 19, 2013
1 parent c4db4fa commit e78f2e2
Show file tree
Hide file tree
Showing 72 changed files with 373 additions and 540 deletions.
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
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
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
4 changes: 2 additions & 2 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 Down
8 changes: 4 additions & 4 deletions src/libcore/hashmap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,10 +290,10 @@ pub mod linear {

impl<K:Hash + IterBytes + Eq,V> Container for LinearMap<K, V> {
/// Return the number of elements in the map
pure fn len(&self) -> uint { self.size }
pure fn len(&const self) -> uint { self.size }

/// Return true if the map contains no elements
pure fn is_empty(&self) -> bool { self.len() == 0 }
pure fn is_empty(&const self) -> bool { self.len() == 0 }
}

impl<K:Hash + IterBytes + Eq,V> Mutable for LinearMap<K, V> {
Expand Down Expand Up @@ -555,10 +555,10 @@ pub mod linear {

impl<T:Hash + IterBytes + Eq> Container for LinearSet<T> {
/// Return the number of elements in the set
pure fn len(&self) -> uint { self.map.len() }
pure fn len(&const self) -> uint { self.map.len() }

/// Return true if the set contains no elements
pure fn is_empty(&self) -> bool { self.map.is_empty() }
pure fn is_empty(&const self) -> bool { self.map.is_empty() }
}

impl<T:Hash + IterBytes + Eq> Mutable for LinearSet<T> {
Expand Down
4 changes: 2 additions & 2 deletions src/libcore/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1116,7 +1116,7 @@ pub struct BytesWriter {
impl Writer for BytesWriter {
fn write(&self, v: &[const u8]) {
let v_len = v.len();
let bytes_len = self.bytes.len();
let bytes_len = vec::uniq_len(&const self.bytes);
let count = uint::max(bytes_len, self.pos + v_len);
vec::reserve(&mut self.bytes, count);
Expand All @@ -1131,7 +1131,7 @@ impl Writer for BytesWriter {
}
fn seek(&self, offset: int, whence: SeekStyle) {
let pos = self.pos;
let len = self.bytes.len();
let len = vec::uniq_len(&const self.bytes);
self.pos = seek_in_buf(offset, pos, len, whence);
}
fn tell(&self) -> uint { self.pos }
Expand Down
6 changes: 2 additions & 4 deletions src/libcore/mutable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ pub fn unwrap<T>(m: Mut<T>) -> T {
pub impl<T> Data<T> {
fn borrow_mut<R>(&self, op: &fn(t: &mut T) -> R) -> R {
match self.mode {
Immutable => fail!(fmt!("%? currently immutable",
self.value)),
Immutable => fail!(~"currently immutable"),
ReadOnly | Mutable => {}
}

Expand All @@ -62,8 +61,7 @@ pub impl<T> Data<T> {

fn borrow_imm<R>(&self, op: &fn(t: &T) -> R) -> R {
match self.mode {
Mutable => fail!(fmt!("%? currently mutable",
self.value)),
Mutable => fail!(~"currently mutable"),
ReadOnly | Immutable => {}
}

Expand Down
8 changes: 4 additions & 4 deletions src/libcore/option.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,14 +228,14 @@ pub pure fn while_some<T>(x: Option<T>, blk: &fn(v: T) -> Option<T>) {
}

#[inline(always)]
pub pure fn is_none<T>(opt: &Option<T>) -> bool {
pub pure fn is_none<T>(opt: &const Option<T>) -> bool {
//! Returns true if the option equals `none`

match *opt { None => true, Some(_) => false }
}

#[inline(always)]
pub pure fn is_some<T>(opt: &Option<T>) -> bool {
pub pure fn is_some<T>(opt: &const Option<T>) -> bool {
//! Returns true if the option contains some value

!is_none(opt)
Expand Down Expand Up @@ -333,11 +333,11 @@ impl<T> MutableIter<T> for Option<T> {
pub impl<T> Option<T> {
/// Returns true if the option equals `none`
#[inline(always)]
pure fn is_none(&self) -> bool { is_none(self) }
pure fn is_none(&const self) -> bool { is_none(self) }

/// Returns true if the option contains some value
#[inline(always)]
pure fn is_some(&self) -> bool { is_some(self) }
pure fn is_some(&const self) -> bool { is_some(self) }

/**
* Update an optional value by optionally running its content by reference
Expand Down
12 changes: 6 additions & 6 deletions src/libcore/ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -223,20 +223,20 @@ pub unsafe fn array_each<T>(arr: **T, cb: &fn(*T)) {
}

pub trait Ptr<T> {
pure fn is_null(&self) -> bool;
pure fn is_not_null(&self) -> bool;
pure fn is_null(&const self) -> bool;
pure fn is_not_null(&const self) -> bool;
pure fn offset(&self, count: uint) -> Self;
}

/// Extension methods for immutable pointers
impl<T> Ptr<T> for *T {
/// Returns true if the pointer is equal to the null pointer.
#[inline(always)]
pure fn is_null(&self) -> bool { is_null(*self) }
pure fn is_null(&const self) -> bool { is_null(*self) }

/// Returns true if the pointer is not equal to the null pointer.
#[inline(always)]
pure fn is_not_null(&self) -> bool { is_not_null(*self) }
pure fn is_not_null(&const self) -> bool { is_not_null(*self) }

/// Calculates the offset from a pointer.
#[inline(always)]
Expand All @@ -247,11 +247,11 @@ impl<T> Ptr<T> for *T {
impl<T> Ptr<T> for *mut T {
/// Returns true if the pointer is equal to the null pointer.
#[inline(always)]
pure fn is_null(&self) -> bool { is_null(*self) }
pure fn is_null(&const self) -> bool { is_null(*self) }

/// Returns true if the pointer is not equal to the null pointer.
#[inline(always)]
pure fn is_not_null(&self) -> bool { is_not_null(*self) }
pure fn is_not_null(&const self) -> bool { is_not_null(*self) }

/// Calculates the offset from a mutable pointer.
#[inline(always)]
Expand Down
4 changes: 2 additions & 2 deletions src/libcore/repr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ impl TyVisitor for ReprVisitor {
}

fn visit_enum_variant_field(&self, i: uint, inner: *TyDesc) -> bool {
match self.var_stk[self.var_stk.len() - 1] {
match self.var_stk[vec::uniq_len(&const self.var_stk) - 1] {
Degenerate | TagMatch => {
if i != 0 {
self.writer.write_str(", ");
Expand All @@ -517,7 +517,7 @@ impl TyVisitor for ReprVisitor {
_disr_val: int,
n_fields: uint,
_name: &str) -> bool {
match self.var_stk[self.var_stk.len() - 1] {
match self.var_stk[vec::uniq_len(&const self.var_stk) - 1] {
Degenerate | TagMatch => {
if n_fields > 0 {
self.writer.write_char(')');
Expand Down
2 changes: 1 addition & 1 deletion src/libcore/task/spawn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ type TaskGroupInner = &'self mut Option<TaskGroupData>;

// A taskgroup is 'dead' when nothing can cause it to fail; only members can.
pure fn taskgroup_is_dead(tg: &TaskGroupData) -> bool {
(&tg.members).is_empty()
(&const tg.members).is_empty()
}

// A list-like structure by which taskgroups keep track of all ancestor groups
Expand Down

0 comments on commit e78f2e2

Please sign in to comment.