-
Notifications
You must be signed in to change notification settings - Fork 0
Doc detailed release notes
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.
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.
Rust is in the middle of a transition to a new iterator mechanism. Instead of using higher-order functions like Ruby ("internal iterators") Rust will use Iterator
types, like Java ("external iterators"). There was an excellent blog post recently exploring the pros and cons of each. External iterators are more flexible and their code is believed to be faster to generate.
Note that the for
protocol is still using the old iteration protocol, but it will be updated in the next release. For compatibility with for
, Iterator
s have an advance
method that converts them to the old-style.
let v = [0, 1];
for v.iter().advance |i| { ... }
There is a new tutorial on this topic.
Further reading:
- https://mail.mozilla.org/pipermail/rust-dev/2013-June/004364.html
- http://thread.gmane.org/gmane.comp.lang.rust.devel/4528
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.
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).
- The
self
type parameter in traits was renamedSelf
(capital) to help differentiate it from the keywordself
- The implicit
self
parameter in trait methods is now deprecated. Methods should now specify theirself
parameter explicitly. - The
Durable
trait was removed; it is synonymous, as a type-bound, with thestatic
lifetime. - Trailing sigils on closure types such as
fn@
,fn~
andfn&
were removed in favour of the more-consistent leading sigils@fn
,~fn
and&fn
- The
const
keyword was renamed tostatic
, 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
andassert
keywords were replaced with macrosfail!()
andassert!()
. - The
log
keyword was removed; usedebug!()
,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 asstruct Foo(int);
instead - The visibility modifiers (
pub
andpriv
) 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 forclone
anditer_bytes
) was removed; use#[deriving(Eq)]
instead. Multiple traits can be specified in the same attribute, as in#[deriving(Eq, Clone, IterBytes)]
.
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.
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>() {
}
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 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();
}
}
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
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 };
}
- 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 moduleMut<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.
-
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 writtenuse std::libc; use std::libc::raw;
. - The
super
andself
can be used in paths to refer to parent modules and the current module, respectively. -
extern mod
statements must occur beforeuse
statements, anduse
statements can now shadowextern mod
statements (rather than vice-versa, as was the case in 0.5). This change makes the shadowing behavior betweenextern mod
,use
and module-local item definitions consistent with the (required) order of writing them.
- 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. Whilemain
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.
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:
-
volatile - specifying this is analogous to
__asm__ __volatile__ (...)
in gcc/clang. -
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
-
intel - use intel syntax instead of the default AT&T.
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).
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();
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.
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 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
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.
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.
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.
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.
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.
This release tried to narrow down the set of keywords and the current keywords are expected to be close to final.
Keyword changes:
-
iface
becametrait
-
ret
becamereturn
-
alt
becamematch
-
again
was removed in favor of reusingloop
to continue, i.e.loop { if foo { loop; } }
-
import
andexport
were removed in favor ofpub
andpriv
item-level visibility (see below). -
class
was removed in favor of combinations ofstruct
,impl
andtrait
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
andstatic
are currently parsed as contextual keywords, but are expected to not be keywords in the future. -
assert
,log
, andfail
are likely to be converted to macros. -
drop
may become a trait rather than a keyword, as mightconst
.
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.
(#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.
(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
.
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.
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.
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()
.
(#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
.
(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 = (...)
.
(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.
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.
- Region names are specified as
&r/T
instead of&r.T
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.
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.
(#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.
(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
}
}
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.
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.
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.
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();
}
}
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.
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:
We have a new syntax for ignoring variant fields in patterns
alt my_enum {
i_could_match_like_this(_, _, _, _, _, _) {
}
but_would_rather_like_this(*) {
}
}
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
}
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
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:
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")));
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.
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.
The first line of a Rust source file can contain a shebang
#! /usr/local/bin/rustx
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.
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.
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"
Need to write about:
- UV-related APIs
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.
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.
Initial release.