Skip to content

Doc detailed release notes

Tim Chevalier edited this page Jun 27, 2013 · 131 revisions

This page covers releases in more detail than the bullet-point list given in the RELEASES.txt file in the source distribution, in particular focusing on language level changes that will be immediately visible and/or disruptive to users trying to keep their Rust code compiling and working right between releases. It is intended to hold copied, cleaned-up versions of entries from the development roadmap as they are completed, to help users plan migration on their own code.

0.7 July 2013

Cloning vs. copying

The copy keyword is being removed in favor of traits. Explicit copying is now performed with the clone method of the Clone trait, which can automatically be derived with #[deriving(Clone)]. Clone is part of the Rust prelude so is always in scope.

#[deriving(Clone)]
struct PopsicleToken;

let my_token = PopsicleToken;
let your_token = my_token.clone();

Clone makes shallow copies of managed pointers and other shared pointer types like Rc and ARC. For deep copies use the DeepClone trait and the deep_clone method.

Iterators

https://mail.mozilla.org/pipermail/rust-dev/2013-June/004364.html
http://thread.gmane.org/gmane.comp.lang.rust.devel/4528
http://journal.stuffwithstuff.com/2013/01/13/iteration-inside-and-out/

rustpkg

While rustpkg is still in an experimental state, there are a number of improvements; see the rustpkg manual for more details.

  • rustpkg uses a URL-like package ID to specify a local or remote package, and has the ability to download remote packages from github.
  • rustpkg infers package IDs from directory structure; packages need no longer declare their identity explicitly
  • Package IDs can have explicit versions attached.
  • Package scripts (pkg.rs) are no longer required; rustpkg infers crates to build based on reasonable defaults.
  • Package scripts can explicitly invoke the default build logic.
  • rustpkg requires a specific directory structure for workspaces.
  • rustpkg infers dependencies from extern mod directives, and doesn't require -L flags on the command line for finding libraries.

Numeric traits

Macros?

0.6 April 2013

This was a very busy development cycle focused on completing as many of the planned language-level changes as possible in the release time-window. While we cannot promise that this is the last time there will be incompatible changes, the great majority of anticipated language-level changes are complete in this version. We expect subsequent releases before a beta and final 1.0 to be more focused on non-language-level work (performance, libraries, packaging and building, runtime system) with only modest language-level changes as we discover bugs and areas requiring residual polish (primarily in the trait system, macro system, and borrow check).

Minor syntax changes

  • The self type parameter in traits was renamed Self (capital) to help differentiate it from the keyword self
  • The implicit self parameter in trait methods is now deprecated. Methods should now specify their self parameter explicitly.
  • The Durable trait was removed; it is synonymous, as a type-bound, with the static lifetime.
  • Trailing sigils on closure types such as fn@, fn~ and fn& were removed in favour of the more-consistent leading sigils @fn, ~fn and &fn
  • The const keyword was renamed to static, to accommodate for future unsafe mutable static variables.
  • The export keyword, formerly deprecated, was removed; pub use exported::path; is synonymous.
  • The move keyword was removed; owned types are always passed and assigned by moving now.
  • The fail and assert keywords were replaced with macros fail!() and assert!().
  • The log keyword was removed; use debug!(), error!() and similar macros.
  • Single-element tuples, denoted (T,) were added as a special-case to help with some macros.
  • "Newtype" enums (those with a single variant) such as enum Foo = int; were removed; use tuple structs such as struct Foo(int); instead
  • The visibility modifiers (pub and priv) are no longer permitted on trait implementations. They were redundant with the visibility modifiers on trait and type declarations themselves.
  • The #[deriving_eq] attribute (and the related ones for clone and iter_bytes) was removed; use #[deriving(Eq)] instead. Multiple traits can be specified in the same attribute, as in #[deriving(Eq, Clone, IterBytes)].

Inherited mutability

The mut keyword is no longer permitted in ~mut T, [mut T], or in fields of structures; in all cases mutability is controlled by mutability of the owner (inherited mutability). So things written like this in 0.5:

struct Foo {
   mut x: int,
   mut y: int
}
fn main() {
  let z = Foo { x: 10, y: 11 };
  z.y = 12;
}

must now be written like this in 0.6:

struct Foo {
   x: int,
   y: int
}
fn main() {
  let mut z = Foo { x: 10, y: 11 };
  z.y = 12;
}

This change permits reasoning about mutability of an owned structure by reasoning about the mutability of its owner, which in turn means many structures support "freezing" and "thawing": acquiring mutator-methods (statically) when held in a mutable owner, and losing those methods (statically) when moved to an immutable owner.

trait bounds are separated by +

A function like this in 0.5:

fn foo<T:X Y Z, U:Q>() {
}

is written like this in 0.6:

fn foo<T:X + Y + Z, U:Q>() {
}

Trait impls use for

A trait implementation that was written this way in 0.5:

impl Ty : Trait { 
}

is written this way in 0.6:

impl Trait for Ty { 
}

Foreign module changes

Foreign modules (those declared extern) have been simplified to anonymous extern blocks, and all extern functions are now unsafe. This means that code like this in 0.5:

extern mod libc {
    fn getc() -> c_char;
}
fn main() {
    let x = libc::getc();
}

must be written this way in 0.6:

extern {
    fn getc() -> c_char;
}
fn main() {
    unsafe {
        let x = getc();
    }
}

Vector pattern-matching enhancements

Vectors can now be matched against any combination of prefix, suffix and variable-length remainder patterns. The vector is destructured into the variable names given: the variable-length remainder is bound to a slice:

  • [a, b..] to match a vector prefix
  • [..a, b] to match a vector suffix
  • [a, ..b, c] to match both prefix and suffix

Structural records removed

This release completes the consolidation of older "structural records" (those lacking a constructor name, denoted directly as {field: type, ...}) with the newer "nominal structs". Old code written like this in 0.5:

type Cat = {
    name: ~str,
    color: Color
};

fn main() {
    let x = { name: ~"fluffy", color: Grey };
}

Must be written this way in 0.6:

struct Cat {
    name: ~str,
    color: Color
}

fn main() {
    let x = Cat { name: ~"fluffy", color: Grey };
}

Changes to the borrow check and borrowed pointers

  • Borrowing a mutable managed pointer (i.e. passing @mut T to an argument of type &T or &mut T) changed. The borrow is now performed with a dynamic (runtime) write-barrier check that can fail if multiple borrows are performed on the same value. In other words, the type @mut T now behaves like the library module Mut<T> did in 0.5. This change means that a class of potential errors -- multiple mutable aliases of the same memory -- that was formerly prohibited by a conservative static rule in 0.5 will be left to a dynamic check at runtime in 0.6. Experience with the rule present in 0.5 and before demonstrated that the static rule was too restrictive to be useful in practice. We will monitor the resulting semantics carefully to see if the new dynamic failure mode occurs frequently in practice.

Further adjustments to name resolution

  • use statements are now crate-relative by default, meaning that they resolve from the "top" of the crate.
  • As a specific corollary: "chained" use statements -- those that refer to abbreviated names when finding targets -- are no longer legal. For example: use std::libc; use libc::raw; must now be written use std::libc; use std::libc::raw;.
  • The super and self can be used in paths to refer to parent modules and the current module, respectively.
  • extern mod statements must occur before use statements, and use statements can now shadow extern mod statements (rather than vice-versa, as was the case in 0.5). This change makes the shadowing behavior between extern mod, use and module-local item definitions consistent with the (required) order of writing them.

Minor semantic changes

  • the nil type () is now properly 0 bytes
  • the "main function" of an executable crate -- where execution begins -- does not need to be called main anymore. While main is the default, any function marked with the attribute #[main] will override this behavior.
  • The default type of an inferred closure (such as |x| x+1) is now &fn rather than @fn as it was in 0.5.

Inline Assembly

Rust now supports using inline assembly through the asm! macro. The syntax roughly matches that of gcc/clang:

asm!(assembly template
   : output operands
   : input operands
   : clobbers
   : options
   );

asm! must be wrapped in an unsafe block.

Assembly Template

The assembly template is the only required parameter and must be a literal string (i.e "")

asm!("NOP");

Output operands, input operands, clobbers and options are all optional but you must add the right number of : if you skip them:

asm!("xor %eax, %eax"
    :
    :
    : "eax"
   );

Whitespace also doesn't matter:

asm!("xor %eax, %eax" ::: "eax");

Operands

Input and output operands follow the same format: : "constraints1"(expr1), "constraints2"(expr2), ...". Output operand expressions must be mutable lvalues:

fn add(a: int, b: int) -> int {
    let mut c = 0;
    unsafe {
        asm!("add $2, $0"
             : "=r"(c)
             : "0"(a), "r"(b)
             );
    }
    c
}

Clobbers

Some instructions modify registers which might otherwise have held different values so we use the clobbers list to indicate to the compiler not to assume any values loaded into those registers will stay valid.

// Put the value 0x200 in eax
asm!("mov $$0x200, %eax" : /* no outputs */ : /* no inputs */ : "eax");

Input and output registers need not be listed since that information is already communicated by the given constraints. Otherwise, any other registers used either implicitly or explicitly should be listed.

If the assembly changes the condition code register cc should be specified as one of the clobbers. Similarly, if the assembly modifies memory, memory should also be specified.

Options

The last section, options is specific to Rust. The format is comma separated literal strings (i.e :"foo", "bar", "baz"). It's used to specify some extra info about the inline assembly:

Current valid options are:

  1. volatile - specifying this is analogous to __asm__ __volatile__ (...) in gcc/clang.

  2. alignstack - certain instructions expect the stack to be aligned a certain way (i.e SSE) and specifying this indicates to the compiler to insert its usual stack alignment code

  3. intel - use intel syntax instead of the default AT&T.

0.5 December 2012

This was a fairly slow development cycle that focused on implementing more trait features, such as trait inheritance and static methods, with the goal of enabling more expressive standard libraries. This version more-or-less completes Rust's long transition to a linear type system, with non-copyable types moving automatically (the move keyword is deprecated).

Explicit self

Work continues on requiring that instance methods always use explicit self-type declarations, and now self, &self, ~self, and @self are all valid self types and should work. Methods with explicit self are declared like fn foo(self, arg1: Type1, arg2: Type2) { ... }. The old method declaration syntax, without a self type, is deprecated and will likely be removed in 0.6.

struct MyType { ... }

impl MyType {
    fn i_need_a_managed_box(@self) { ... }
    fn i_need_an_owned_box(~self) { ... }
}

let managed_value = @MyType { ... };
managed_value.i_need_a_managed_box();

Self types also have correct support for move semantics, enabling some nice patterns for unique types.

enum Option<T> { Some(T), None }
impl<T> Option<T> {
    fn unwrap(self) -> T {
        // Extract the value from the option and return it.
        // There is no copying here as these operations all
        // move by default now for unique types.
        match self {
            Some(v) => v,
            None => fail
        }
    }
}

fn maybe_get_value() -> Option<MyType> { ... }

let value = maybe_get_value().unwrap();

Trait inheritance

Traits may now declare supertraits. When a trait has supertraits then any type which implements the subtrait must also implement the supertrait. Trait inheritance allows a group of traits to be treated as a single trait in bounded type parameters.

trait Add {
  fn add(&self, other: &self) -> self;
}
trait Sub {
  fn sub(&self, other: &self) -> self;
}
trait Num: Add Sub { }

The methods of each trait must still be implemented seperately.

impl MyNumType: Add {
  fn add(&self, other: &MyNumType) -> MyNumType { ... }
}
impl MyNumType: Sub {
  fn sub(&self, other: &MyNumType) -> MyNumType { ... }
}
impl MyNumType: Num { }

Then the constrained trait may be used in type parameter constraints in place of its supertraits.

fn calculate<T: Num>(val1: T, val2: T, val3: T) -> T {
  val1.add(&val2).sub(&val3)
}

Note that supertrait methods are not yet available on subtrait 'object' types.

// This doesn't work yet
let t = &MyNumType { ... };
let num = t = &Num;
num.add(othernum);

Furthermore, trait inheritance is not yet aware of the "kind" traits, so using Copy, etc. as supertraits will not work as expected.

Static method resolution

In 0.4 static methods 'leaked' out of the traits in which they were defined and were resolved in the outer module scope. This would mean that two sibling traits couldn't define a static method with the same name.

// Couldn't do this before because `bar` actually exists in the outer scope
trait Foo<T> { static fn bar() -> T; }
trait Baz<T> { static fn bar() -> T; }

fn quux<T>() -> T {
    return bar();
}

In 0.5 the module and type namespaces are merged and static methods are resolved under the name of the trait in which they are defined.

// Now this is valid
trait Foo<T> { static fn bar() -> T; }
trait Baz<T> { static fn bar() -> T; }

fn quux<T>() -> T {
    // Now trait names may be used in paths to access static methods
    return Foo::bar();
}

// Likewise, static methods on types (anonymous traits) may be accessed via the type name
struct Swizzle { ... }
impl Swizzle { static fn bar() -> T; }

fn swozzle<T>() -> T {
    return Swizzle::bar();
}

Path resolution

Path resolution has changed significantly to keep in-scope identifiers from leaking from parent modules to submodules. The old behavior, particularly when dealing with large projects with many files, made knowing what was in scope very difficult and prone to error.

In response, we're making a change to how imports (use statements) are resolved. They are now resolved relative to the top of the crate by default, and can be thought of as absolute paths through the crate's module namespace. Paths may be prefixed with the contextual keywords self and super to modify how the lookup is performed.

extern mod std;

mod foo {
    // Bring `net` into scope
    use std::net;

    fn f() { ... }

    // This submodule no longer inherits the scope of the outer module
    mod bar {
        // Need to import `net` again to use it
        use std::net;
    }

    mod baz {
        // We can also use `super` to get at `foo`s import
        use super::net;
    }

    mod quux {
        // Can also use `self` to import from child modules
        use self::boo::net;

        mod boo {
            pub use std::net;
        }
    }
}

Note: There will be related changes to path resolution in other contexts as well in the next release

Note: self and super will likely be promoted to full keywords (not contextual keywords) in the next release

Automatically-derived trait implementations

Implementations of the Eq and IterBytes can be automatically derived using syntax extensions (types that implement IterBytes can automatically implement Hash, so can be used in hash tables).

#[deriving_eq]
#[deriving_iter_bytes]
struct Foo {
  bar: int
}

This should work on all struct and enum types, and does what you would likely expect, delegating to the corresponding impls of each subcomponent in turn.

Condition handling

This release adds a new API for dealing with errors, core::condition. Unlike the concept of exceptions in other languages, conditions are handled at the site where they are raised rather than the site where they are caught. Failure to handle a condition results in task failure.

// A condition has a name, input type and output type.
// The input type contains details of the condition, passed to a handler.
// The output type is what the handler will return if it handles the condition.
condition! {
    missing_input_file : Path -> Reader;
}

// Install the handler
do missing_input_file::cond.trap(|pathname| {
    // Handle a missing input file by providing a fake empty reader.
    io::BytesReader { bytes: bytes, pos: 0u } as Reader
}).in {
    // The condition handler is valid over the dynamic extent
    // of the block passed to trap.
    foo(&Path("/nonexistent"))
}

fn foo(p: &Path) {
    let r = if p.exists() {
        io::file_reader(p)
    } else {
        // If there's a handler in one of our callers, this
        // will evaluate to the handler's return value and
        // we will carry on, no unwinding.
        missing_input_file::cond.raise(p)
    }
    let v = r.read();
    // ...
}

The standard library has not yet been updated to make use of conditions.

Other important changes

The Send trait, one of the built-in 'kinds', is now called Owned. Owned types contain no managed or borrowed pointers. The little-known trait previously called Owned is now called Durable. Durable types contain no borrowed pointers (though in the future they will probably allow borrowed pointers to the static region). All Owned types are Durable.

The declarative language for .rc files has been removed. The biggest practical implication of this change is that, for projects using 'companion modules', a project.rc combined with project.rs, the companion module (project.rs) will not be loaded automatically. You should copy the contents of project.rs into project.rc and delete project.rs.

The move keyword is no longer needed under normal circumstances and should be considered deprecated. Types that are not implicitly copyable now move by default.

0.4 October 2012

This version was focused on completing as many disruptive syntax changes as possible, including changing and reducing keywords, beefing up traits and removing classes, changing how imports and exports are handled, and moving to a camel case convention for types.

Borrowed pointers matured and replaced argument modes in some of the libraries. A number of new concurrency features were added.

Camel cased types

We have a new convention that requires that types (and enum variants, which are not yet types) be camel cased. The entire core and standard libraries have been converted. We generally prefer that acronyms not be written with all caps, so e.g. the standard URL type is written Url.

There is a lint check for this called non_camel_case_types but it is disabled by default.

Keywords

This release tried to narrow down the set of keywords and the current keywords are expected to be close to final.

Keyword changes:

  • iface became trait
  • ret became return
  • alt became match
  • again was removed in favor of reusing loop to continue, i.e. loop { if foo { loop; } }
  • import and export were removed in favor of pub and priv item-level visibility (see below).
  • class was removed in favor of combinations of struct, impl and trait

The current keywords are as follows.

as assert
break
const copy
do drop
else enum extern
fail false fn for
if impl
let log loop
match mod move mut
priv pub pure
ref return
self static struct
true trait type
unsafe use
while

Notes:

  • be is not a keyword, but is reserved for possible future use.
  • self and static are currently parsed as contextual keywords, but are expected to not be keywords in the future.
  • assert, log, and fail are likely to be converted to macros.
  • drop may become a trait rather than a keyword, as might const.

Structs replace classes

Classes were overhauled; the class syntax was removed from the language, in favor of method-less structs combined with method-bearing impls. The new struct syntax is very simple:

struct Cat {
  name: ~str,
  tail_color: KittyColor 
}

let my_cat = Cat {
  name: ~"Morton",
  tail_color: KittyColorBlack
};

// Methods for all types are defined in impls
impl Cat {
  fn meow() { }
}

my_cat.meow();

There is no explicit constructor syntax. The current, and likely temporary, convention for constructors is to create a function with the same name as the type. In the future constructors will probably be defined with static new methods.

// Current convention for defining constructors
fn MyStruct() -> MyStruct {
  MyStruct {
    field1: initial_value_of_field1(),
    field2: initial_value_of_field2()
  }
}

let m = MyStruct();

// Potential future convention for defining constructors
impl MyStruct {
  // Here, the staticness of the function is indicated by the lack of
  // an explicit self-type, another in-progress change.
  fn new() -> MyStruct {
    MyStruct {
      field1: initial_value_of_field(),
      field2: initial_value_of_field()
    }
  }

  ...
}

let n = MyStruct::new();

The last remnants of classes, destructors are temporarily implemented with drop blocks on structs. In future releases destructors will be implementations of a Drop trait.

// How to write a destructor today
struct MyStruct {
  field1: Field1Type,

  drop {
    // Run destructory code here
  }
}

// How to write a destructor in hypothetical future-Rust
impl MyStruct : Drop {
  fn drop() {
  }
}

See also pcwalton's proposal.

Interfaces extended to full traits

(#2794) Traits are interface-like units of behavior that carry method implementations and requirements on the self-type; they can be used to achieve code reuse without introducing much of the pain that comes from conventional inheritance: they can be mixed in any order, can be declared independent from the type they affect, and are compiled independently for each type that implements them (indeed, will inline and specialize exactly as any other function will).

This work involved replacing the iface keyword with trait and supporting full method implementations within traits, not just signatures. The terms employed by Rust's OO system are therefore now struct, trait and impl. We believe this will be the final set of terms.

Implementation coherence enforced

(pcwalton:impl-coherence) Previously each impl had a name and when a client called a method on an impl, the code dispatched-to was chosen based on the impl imported into the client code's scope. This was chosen as a way to be unambiguous about selecting implementations -- a problem in any typeclass system -- but in practice it has been very confusing for users: many are unable to tell why a method can or cannot be seen due to the presence or absence of imports.

Now every type can have at most one impl defined for each trait, and that definition must occur either in the crate defining the type or the crate defining the trait.

Match arms

The arms of match (previously alt) expressions have a new syntax. Each case is now followed by a fat arrow, =>, and the block is optional. Cases are separated by commas.

// The new syntax is quite compact for one-line cases
match foo {
  bar => baz,
  quux => fail
}

match foo {
  bar => {
    baz
  } // Note: no comma needed when using braces
  quux => {
    fail
  }
}

See also #3057.

Explicit self

We are in the process of generalizing traits so that they can include both methods (which take a receiver) and functions (which do not). To that end, we have added a syntax called explicit self. In the explicit self system, methods are identified because their first parameter is called self (similar to Python). Optionally, the parameter can include a sigil indicating what kind of pointer should be used:

struct Foo { ... }
impl Foo {
    fn method0(self, ...);       // self has type `Foo`
    fn method1(&self, ...);      // self has type `&Foo`
    fn method2(&mut self, ...);  // self has type `&mut Foo`
    fn method3(@self, ...);      // self has type `@Foo`
    fn method4(~self, ...);      // self has type `~Foo`
}

Explicit self can also appear in trait declarations. Explicit self is currently optional but is expected to become mandatory in a future release.

Static methods

Traits can now include methods that do not make use of a receiver. Such functions are (currently) labeled with the keyword static. Static functions are (currently) exported as functions in the module which contains the trait where they were declared. Here is an example:

trait FromInt {
    static fn from_int(i: int) -> self;
}

struct Foo { v: int }
impl Foo: FromInt {
    static fn from_int(i: int) -> Foo {
        Foo { v: i }
    }
}

fn main() {
    let x: Foo = from_int(3);
    io::println(fmt!("%?", x));
}

In the future, when explicit self is mandatory, static functions will be designated by the absence of a self receiver. They are also expected to become members of the trait itself, so you would write FromInt::from_int().

Transitioning import/export to pub/priv

(#2300) There was some tension in readability between "ease of scanning a module's exports" and "ease of reading the code and knowing which item is exported when you're looking at it." Ultimately we came down on the side of maintainability: that it's less work for a maintainer to mark the items where they occur, rather than scrolling back and forth between export-list and item definitions. Along with moving exports to the items themselves, we changed the terms to use the same keywords used for access control in structs: pub and priv.

Change use to mean import, remove import, make crate-linkage use extern mod foo = (...);

(also #2082) Many Rust programmers stub their toes on the difference between import and use; both "read like" they should somehow make-available the elements in the target module. Since removing export (see above) there is an asymmetry in the keywords anyways, so we removed the keyword import, switched use to mean what import previously meant, and now denote crate-linkage through extern mod foo = (...).

Separate form of module import: use mod foo = bar;

(also #2082) This has to do with making the resolve pass coherent. In the old resolve code, resolving modules and resolving items glob-imported from modules was intermixed, and could lead to incoherence of the algorithm. In the new resolve code (landed in 0.3) there is a separation of passes: module-imports are not run through globs, only module-to-module renamings, and are resolved first, and then all imports through modules (including glob-imports) are resolved after. The module-import syntax is therefore separate, written as use mod foo = bar; to reflect this algorithmic separation.

Macro changes

Macros are now invoked with a postfix ! instead of prefix #, as in debug!("foo").

We also have a powerful new way to define macros with the macro_rules! syntax extension. Macros are now based on trees of tokens with balanced braces instead of the full AST expressions the old macro implementation used.

See also the macro tutorial.

Smaller syntax changes

  • Region names are specified as &r/T instead of &r.T

Trait-based operator overloading

Operator overloading is now expressed through a set of traits in the core library. The traits, as well as implementations on standard types can be found in core::ops and core::cmp. The set of overloaded operators has not changed, merely the mechanism used to define overloads.

Hash function improvements

All hash functions in the standard library were replaced. Hashing is now statically dispatched (via traits) to an implementation of SipHash, a fast keyed hash function. All hashtables are also now randomly keyed from Rust's CPRNG (ISAAC, seeded from the operating system PRNG on startup). In practice this should help defend Rust programs against complexity attacks.

Deprecation / removal of argument modes

(#2030) Previous versions of Rust attempted to select optimal default argument-passing behavior (by-reference or by-value) based on type-directed heuristics, with sigils available to override the defaults. This system (called "modes" or "argument modes") was responsible for the presence of the sigils +, -, & and && on function parameters.

In practice this system was simultaneously too confusing to use and too inflexible with respect to scenarios where first-class borrowed pointers were desired (returning by reference, storing non-escaping pointers into structured values, etc.) Therefore in 0.4, as first-class borrowed pointers (the other, more pervasive use of the syntax &T) have matured to be a better alternative in almost all cases, and many of the residual motives for modes made redundant by full monomorphization, we have deprecated (and removed in as much code as possible) the mode system; it should not be visible to users and support for it is only available by opting in with the #[legace_modes] crate attribute.

Inherited mutability

(nmatsakis:mut) After several repeated attempts to treat mutability as a full type constructor, we have settled on treatment as an inherited storage-qualifier. That is, while the mut keyword can still only be used to qualify storage locations (the referents of pointers, variables, fields in structures), by placing mut on a storage location you now implicitly deeply mut-qualify all of the owned (sendable) types within the mutable location. This makes sense if you consider that an "outer" mut qualification could always effectively mutate an inner "immutable" qualification by simply overwriting the entire containing location. This change therefore only makes the rule explicit in the type system; it is in other words a matter of improving the type system's soundness with respect to mutability.

struct S1 {
  field: ~S2;
}

struct S2 {
  intfield: int,
  boxfield: @int
}

let mut s1 = S1 { field: S2 { intfield: 1, boxfield: @2 } };

// s1 is in a mutable slot so we can mutate the fields
s1.field = S2 { intfield: 3, boxfield: @4 };

// We can also reach through owned boxes and mutate their insides
s1.field.intfield = 5;

// Managed boxes are not owned so their interior is not mutable
*s1.field.boxfield = 6; // ERROR

At first glance this might seem like an odd way to deal with mutability, but it seems to work very well with Rust's ownership semantics, and it enables some fascinating patterns that are particularly useful for concurrency. In particular, when combined with explicit self types it allows for 'dual mode' data structures that, under certain circumstances my mutate fields, and under others not, similar to C++ const methods.

struct S {
  foo: int
}

impl S {
  // Method declared with explicit mutable self type
  fn mutate(&mut self) {
    self.foo = 10;
  }

  fn do_not_mutate(&self) {
    // Can't mutate fields because we don't have a mutable self pointer
  }
}

So you can do neat stuff with this.

// LinearMap is an owned map type in core::send_map
// While it lives in a mutable slot we can call methods that mutate the map
let mut map = LinearMap();
map.insert(foo, bar);

// Move it into an immutable slot and the whole structure becomes 'frozen'
let map = move map;
map.insert(foo, bar); // ERROR

// At this point you can do some interesting things like put your entire map
// into a read-only shared-memory container.
let arc_map = ARC(move map); // ARC: 'atomically-reference-counted'

for repeat(50) {
  // Create another handle to the same map
  let arc_clone = arc::clone(arc_map);
  do spawn |move arc_clone| {
    // Read my immutable, shared hash map in parallel with other tasks
  }
}

Task management

The task creation and lifecycle interface was significantly enhanced, adding more flexibility for grouping tasks according to which should die when a single task fails. There are now methods to spawn tasks with bidirectionally-linked, unidirectionally-linked, and unlinked failure propagation. There is also a low-level mechanism for storing task-local data, which will become the basis for some more advanced error handling mechanisms in future versions.

Pipes

A new communication system was added in this cycle, called "pipes". This is a lower-level, higher-performance system based on synchronous, 1:1 communication primitives with owned endpoints. Pipes are much friendlier to the memory subsystem of modern hardware and we expect to transition Rust's libraries to use pipes rather than the port/channel system that shipped in previous releases. This transition is not yet complete, but will happen in over subsequent releases.

0.3 July 2012

Integer-literal suffix inference

Rust has no coercion between integral types, so until this release literals always required an appropriate suffix for types other than int, e.g. 12_u8. This was widely considered an eye-sore and inconvenience. Now these suffixes can be left off in most situations and their type will be correctly inferred.

let random_digits: [u8] = [1, 4, 1, 5, 9, 2, 6];

Read the detailed announcement on the mailing list.

Class stabilization, removal of resources

Classes are ready for use now, and can contain destructors and implement ifaces. The resource type has been removed in favor of class destructors. In addition, both classes and class methods can have type parameters.

iface talky {
    fn speak();
}

iface printable {
    fn print();
}

class talker<T: printable> : talky {

    let word: T;

    new(word: T) {
        self.word = word;
    }

    drop {
        self.speak();
    }

    fn speak() {
        self.word.print();
    }
}

New closure syntax, new do syntax for control-structure-like calls

Closures have a more compact syntax: foo.map( |i| i + 1)

They work with the overhauled for loops and the new do expressions to provide nice sugar for higher-order functions.

for foo.each |i| {
    println(#fmt("%?", i));
}

With no arguments these forms are quite minimal.

do spawn {
    // this is the body of a closure
}

Read full discussion.

New vector types

Until now all vectors have been unique types allocated on the exchange heap, but accidental copying of and allocation of vectors turned out to be a major performance hazard.

0.3 features the full complement of vector types demanded by Rust's memory model - in other words you can put vectors on the stack, the local heap or the exchange heap.

// A unique vector, allocated on the exchange heap
let x: ~[int] = ~[0];
// A shared vector, allocated on the local heap
let y: @[int] = @[0];
// A stack vector, allocated on the stack
let z: &[int] = &[0];

The libraries have not fully caught up to the new vector types.

Note: [] is currently a synonym for ~[]. It's not clear what the former syntax will end up meaning, possibly a slice or fixed-length vector.

Some posts on vectors that have varying relationships to the final implementation:

*-patterns

We have a new syntax for ignoring variant fields in patterns

alt my_enum {
    i_could_match_like_this(_, _, _, _, _, _) {
    }
    but_would_rather_like_this(*) {
    }
}

Doc comments

Rust now has a form of attribute specifically for doc comments. Like other attributes there are different forms depending on whether the documentation is on the outside or the inside of the thing its documenting.

/** Outer doc comment */
fn f() {

/// Outer doc comment
fn g() { }

fn h() {
    /*! Inner doc comment */
}

fn i() {
    //! Inner doc comment
}

Per item control over warnings and errors

Warnings can be disabled (or enabled or elevated to errors) on a per-item basis, whereas before it was per-crate.

#[warn(no_non_implicitly_copyable_typarams)]
fn implicitly() -> ~[str] {
    import std::sort::merge_sort;

    merge_sort(str::eq, ~["not_implicitly_copyable"])
}

In this example, merge_sort will copy the elements of the vector during sorting, but strings may not be copied without writing copy (because they are unique types and copying them involves allocation). In this situation rustc currently emits a warning. As there is no language-level mechanism yet to authorize the copy, sometimes you just have to turn the warning off.

Note that the current syntax for disabling warnings by prefixing "no_" to the naming of the warning is confusing and will change.

The warnings (and their default setting) understood by rustc are:

  • ctypes (warn) - foreign modules should use core::libc types
  • unused_imports (ignore) - disallow imports that are never used
  • while_true (warn) - disallow while true (should use loop)
  • path_statement (warn) - writing a statement that names a value without using it is usually a bug
  • old_vecs (warn) - use of deprecated vec syntax ([], the meaning of which is likely changing to slice instead of unique vector. the current syntax for unique vectors is ~[])
  • old_strs (ignore) - use of deprecated str syntax
  • unrecognized_warning

The following are all related to the in-progress effort to eliminate expensive, implicit copies from the language. The current rule is that types that require allocation to copy (unique boxes) or that contain mutable fields are not implicitly copyable. These are warnings now but will be either errors by default or entirely disallowed by the typesystem in the future.

  • implicit_copies - performing a copy of a non-implicitly copyable type without the copy keyword
  • vecs_not_implicitly_copyable - same as above but for vectors
  • non_implicitly_copyable_typarams - use of generics whose type parameters have the copy kind with a type that is not implicitly copyable

Region progress

There has been a lot of progress in converting Rust to a region-based memory model. Although region pointers are not yet ready for use, internally many of rust's analyses have been rewritten in terms of regions.

See Niko's blog posts about regions:

New syntax extensions

import io::println;

// Typical file info
println(#fmt("%?", #line()));
println(#fmt("%?", #col()));
println(#fmt("%?", #file()));

// The name of the current module, or empty
println(#fmt("%?", #mod()));

let x = 10, y = 15;

// Turn a Rust expression into a string
println(#fmt("%?", #stringify[x + y]));
// Include the contents of a file as a Rust expression
println(#fmt("%?", #include("x_plus_y.rs")));
// Include the contents of a file as a string
println(#fmt("%?", #include_str("x_plus_y.rs")));
// Include the contents of a file as a byte vector
println(#fmt("%?", #include_bin("x_plus_y.rs")));

Const kind

Rust has a new kind, const, that can be used as a bounds on type parameters. A type that is const contains only const fields that are not mutable. A type that is both const and send is appropriate for using in shared-memory concurrency patterns because it is deeply immutable and does not contain local box pointers.

This is currently used by core::arc which provides an atomically reference counted, sendable type that encapsulates a const + send type.

FFI changes

The word 'native' is being expunged from the language since it implies that Rust is not native. Native modules are now declared with the extern keyword.

extern mod cairo {

Rust functions that can be called from native code with the CDECL ABI, previously called 'crust', are now also declared with the `extern' keyword.

extern fn a_native_callback(user_data: *c_void) {
     do_some_crusty_stuff(user_data);
}

These changes are part of a larger plan to overhaul the terminology and syntax around linking.

Shebang

The first line of a Rust source file can contain a shebang

#! /usr/local/bin/rustx

Removed features

be, prove, syntax, note were unimplemented and removed from the language. mutable is now written mut. cont was renamed to again. bind had too much overlap with other closure forms while providing subtly different semantics so was removed. do loops were rarely used, so the do keyword was repurposed. Resources were removed in favor of class destructors.

Reflection system

A preliminary reflection system now exists. Type descriptors contain a compiler-generated function that calls visitor-methods on a predefined intrinsic visitor interface. This enables reflecting on a value without knowing its type (with some supporting library work). Much existing code will gradually shift over to this interface, as it subsumes a number of other tasks the compiler and runtime are currently doing as special cases.

Library improvements

There are more methods available on more basic and core types by default now. Methods are generally preferred over functions now when there is a clear 'self' type.

The standard library has a new time module.

let time: tm = now();

// Convert to a string
println(time.strftime("%D/%M/%Y"));

// tm has some built in conversions
println(time.ctime());   // "Thu Jan  1 00:00:00 1970"
println(time.rfc822());  // "Thu Jan  1 00:00:00 1970"
println(time.rfc3339()); // "2012-02-22T07:53:18-07:00"

TODO

Need to write about:

  • UV-related APIs

0.2 March 2012

Region pointers

These are a new kind of pointer. In this release, they are written with the sigil &, which is slightly ambiguous with the argument-mode sigil & but our longer-term plan is to replace all argument modes with region pointers and remove the concept of argument modes. So this ambiguity was tolerated during this release cycle (we may pick a different sigil later).

Region pointers are cheaper to use than any other sorts of safe pointer in rust; they are statically guaranteed to point to live memory (by construction) so anywhere you are allowed to use them, it is safe to use them, and manipulating them incurs no cost -- not even garbage-collection scanning cost. They are much like C++ &-references, they way they are idiomatically used. We recommend all signatures to functions be written in terms of region pointers whenever possible. The rust compiler will eventually support automatically "borrowing" the other sorts of pointers into region pointers for the duration of a call, such that region pointers can act as a suitable default for callee signatures.

Operating system and libc interfaces

The previous interface was written in terms of a number of per-platform modules in std, each of which exposed a sub-module called libc, as well as some redundant interfaces in std::fs and std::os_fs. There was a lot of redundant and poorly-factored, platform-variable code in here. It was consolidated into three files, core::libc, core::os, and core::fs which were platform-conditionalized on an item-by-item basis.

0.1 January 2012

Initial release.

All Categories:

Clone this wiki locally