Skip to content

Commit

Permalink
librustc: Implement lifetime elision.
Browse files Browse the repository at this point in the history
This implements RFC 39. Omitted lifetimes in return values will now be
inferred to more useful defaults, and an error is reported if a lifetime
in a return type is omitted and one of the two lifetime elision rules
does not specify what it should be.

This primarily breaks two uncommon code patterns. The first is this:

    unsafe fn get_foo_out_of_thin_air() -> &Foo {
        ...
    }

This should be changed to:

    unsafe fn get_foo_out_of_thin_air() -> &'static Foo {
        ...
    }

The second pattern that needs to be changed is this:

    enum MaybeBorrowed<'a> {
        Borrowed(&'a str),
        Owned(String),
    }

    fn foo() -> MaybeBorrowed {
        Owned(format!("hello world"))
    }

Change code like this to:

    enum MaybeBorrowed<'a> {
        Borrowed(&'a str),
        Owned(String),
    }

    fn foo() -> MaybeBorrowed<'static> {
        Owned(format!("hello world"))
    }

Closes rust-lang#15552.

[breaking-change]
  • Loading branch information
pcwalton committed Jul 19, 2014
1 parent 793b142 commit 6f99a27
Show file tree
Hide file tree
Showing 71 changed files with 303 additions and 168 deletions.
8 changes: 5 additions & 3 deletions src/libcollections/dlist.rs
Expand Up @@ -87,12 +87,14 @@ impl<T> Rawlink<T> {
}

/// Convert the `Rawlink` into an Option value
fn resolve_immut(&self) -> Option<&T> {
unsafe { self.p.to_option() }
fn resolve_immut<'a>(&self) -> Option<&'a T> {
unsafe {
mem::transmute(self.p.to_option())
}
}

/// Convert the `Rawlink` into an Option value
fn resolve(&mut self) -> Option<&mut T> {
fn resolve<'a>(&mut self) -> Option<&'a mut T> {
if self.p.is_null() {
None
} else {
Expand Down
4 changes: 2 additions & 2 deletions src/libgraphviz/lib.rs
Expand Up @@ -455,12 +455,12 @@ impl<'a> LabelText<'a> {
}

/// Puts `prefix` on a line above this label, with a blank line separator.
pub fn prefix_line(self, prefix: LabelText) -> LabelText {
pub fn prefix_line(self, prefix: LabelText) -> LabelText<'static> {
prefix.suffix_line(self)
}

/// Puts `suffix` on a line below this label, with a blank line separator.
pub fn suffix_line(self, suffix: LabelText) -> LabelText {
pub fn suffix_line(self, suffix: LabelText) -> LabelText<'static> {
let prefix = self.pre_escaped_content().into_string();
let suffix = suffix.pre_escaped_content();
EscStr(str::Owned(prefix.append(r"\n\n").append(suffix.as_slice())))
Expand Down
2 changes: 1 addition & 1 deletion src/libgraphviz/maybe_owned_vec.rs
Expand Up @@ -109,7 +109,7 @@ impl<'b,T> slice::Vector<T> for MaybeOwnedVector<'b,T> {
}

impl<'a,T> FromIterator<T> for MaybeOwnedVector<'a,T> {
fn from_iter<I:Iterator<T>>(iterator: I) -> MaybeOwnedVector<T> {
fn from_iter<I:Iterator<T>>(iterator: I) -> MaybeOwnedVector<'a,T> {
// If we are building from scratch, might as well build the
// most flexible variant.
Growable(FromIterator::from_iter(iterator))
Expand Down
4 changes: 2 additions & 2 deletions src/libgreen/sched.rs
Expand Up @@ -959,13 +959,13 @@ impl CleanupJob {
type UnsafeTaskReceiver = raw::Closure;
trait ClosureConverter {
fn from_fn(|&mut Scheduler, Box<GreenTask>|) -> Self;
fn to_fn(self) -> |&mut Scheduler, Box<GreenTask>|;
fn to_fn(self) -> |&mut Scheduler, Box<GreenTask>|:'static ;
}
impl ClosureConverter for UnsafeTaskReceiver {
fn from_fn(f: |&mut Scheduler, Box<GreenTask>|) -> UnsafeTaskReceiver {
unsafe { mem::transmute(f) }
}
fn to_fn(self) -> |&mut Scheduler, Box<GreenTask>| {
fn to_fn(self) -> |&mut Scheduler, Box<GreenTask>|:'static {
unsafe { mem::transmute(self) }
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/librustc/middle/trans/basic_block.rs
Expand Up @@ -31,7 +31,7 @@ impl BasicBlock {
}
}

pub fn pred_iter(self) -> Preds {
pub fn pred_iter(self) -> Preds<'static> {
self.as_value().user_iter()
.filter(|user| user.is_a_terminator_inst())
.map(|user| user.get_parent().unwrap())
Expand Down
52 changes: 52 additions & 0 deletions src/librustc/middle/ty.rs
Expand Up @@ -4872,3 +4872,55 @@ pub enum ExplicitSelfCategory {
ByBoxExplicitSelfCategory,
}

/// Pushes all the lifetimes in the given type onto the given list. A
/// "lifetime in a type" is a lifetime specified by a reference or a lifetime
/// in a list of type substitutions. This does *not* traverse into nominal
/// types, nor does it resolve fictitious types.
pub fn accumulate_lifetimes_in_type(accumulator: &mut Vec<ty::Region>,
typ: t) {
walk_ty(typ, |typ| {
match get(typ).sty {
ty_rptr(region, _) => accumulator.push(region),
ty_enum(_, ref substs) |
ty_trait(box TyTrait {
substs: ref substs,
..
}) |
ty_struct(_, ref substs) => {
match substs.regions {
subst::ErasedRegions => {}
subst::NonerasedRegions(ref regions) => {
for region in regions.iter() {
accumulator.push(*region)
}
}
}
}
ty_closure(ref closure_ty) => {
match closure_ty.store {
RegionTraitStore(region, _) => accumulator.push(region),
UniqTraitStore => {}
}
}
ty_nil |
ty_bot |
ty_bool |
ty_char |
ty_int(_) |
ty_uint(_) |
ty_float(_) |
ty_box(_) |
ty_uniq(_) |
ty_str |
ty_vec(_, _) |
ty_ptr(_) |
ty_bare_fn(_) |
ty_tup(_) |
ty_param(_) |
ty_infer(_) |
ty_unboxed_closure(_) |
ty_err => {}
}
})
}

94 changes: 72 additions & 22 deletions src/librustc/middle/typeck/astconv.rs
Expand Up @@ -55,6 +55,7 @@ use middle::lang_items::FnMutTraitLangItem;
use middle::subst::{FnSpace, TypeSpace, SelfSpace, Subst, Substs};
use middle::ty;
use middle::ty_fold::TypeFolder;
use middle::typeck::rscope::{ExplicitRscope, ImpliedSingleRscope};
use middle::typeck::rscope::RegionScope;
use middle::typeck::{TypeAndSubsts, infer, lookup_def_tcx, rscope};
use middle::typeck;
Expand Down Expand Up @@ -931,31 +932,45 @@ fn ty_of_method_or_bare_fn<AC:AstConv>(
Option<ty::ExplicitSelfCategory>) {
debug!("ty_of_method_or_bare_fn");

// new region names that appear inside of the fn decl are bound to
// that function type
// New region names that appear inside of the arguments of the function
// declaration are bound to that function type.
let rb = rscope::BindingRscope::new(id);

// `implied_output_region` is the region that will be assumed for any
// region parameters in the return type. In accordance with the rules for
// lifetime elision, we can determine it in two ways. First (determined
// here), if self is by-reference, then the implied output region is the
// region of the self parameter.
let mut explicit_self_category_result = None;
let self_ty = opt_self_info.and_then(|self_info| {
// Figure out and record the explicit self category.
let explicit_self_category =
determine_explicit_self_category(this, &rb, &self_info);
explicit_self_category_result = Some(explicit_self_category);
match explicit_self_category {
ty::StaticExplicitSelfCategory => None,
ty::ByValueExplicitSelfCategory => {
Some(self_info.untransformed_self_ty)
}
ty::ByReferenceExplicitSelfCategory(region, mutability) => {
Some(ty::mk_rptr(this.tcx(), region,
ty::mt {ty: self_info.untransformed_self_ty,
mutbl: mutability}))
}
ty::ByBoxExplicitSelfCategory => {
Some(ty::mk_uniq(this.tcx(), self_info.untransformed_self_ty))
let (self_ty, mut implied_output_region) = match opt_self_info {
None => (None, None),
Some(self_info) => {
// Figure out and record the explicit self category.
let explicit_self_category =
determine_explicit_self_category(this, &rb, &self_info);
explicit_self_category_result = Some(explicit_self_category);
match explicit_self_category {
ty::StaticExplicitSelfCategory => (None, None),
ty::ByValueExplicitSelfCategory => {
(Some(self_info.untransformed_self_ty), None)
}
ty::ByReferenceExplicitSelfCategory(region, mutability) => {
(Some(ty::mk_rptr(this.tcx(),
region,
ty::mt {
ty: self_info.untransformed_self_ty,
mutbl: mutability
})),
Some(region))
}
ty::ByBoxExplicitSelfCategory => {
(Some(ty::mk_uniq(this.tcx(),
self_info.untransformed_self_ty)),
None)
}
}
}
});
};

// HACK(eddyb) replace the fake self type in the AST with the actual type.
let input_tys = if self_ty.is_some() {
Expand All @@ -964,12 +979,47 @@ fn ty_of_method_or_bare_fn<AC:AstConv>(
decl.inputs.as_slice()
};
let input_tys = input_tys.iter().map(|a| ty_of_arg(this, &rb, a, None));
let self_and_input_tys: Vec<_> =
self_ty.move_iter().chain(input_tys).collect();

// Second, if there was exactly one lifetime (either a substitution or a
// reference) in the arguments, then any anonymous regions in the output
// have that lifetime.
if implied_output_region.is_none() {
let mut self_and_input_tys_iter = self_and_input_tys.iter();
if self_ty.is_some() {
// Skip the first argument if `self` is present.
drop(self_and_input_tys_iter.next())
}

let self_and_input_tys = self_ty.move_iter().chain(input_tys).collect();
let mut accumulator = Vec::new();
for input_type in self_and_input_tys_iter {
ty::accumulate_lifetimes_in_type(&mut accumulator, *input_type)
}
if accumulator.len() == 1 {
implied_output_region = Some(*accumulator.get(0));
}
}

let output_ty = match decl.output.node {
ast::TyInfer => this.ty_infer(decl.output.span),
_ => ast_ty_to_ty(this, &rb, &*decl.output)
_ => {
match implied_output_region {
Some(implied_output_region) => {
let rb = ImpliedSingleRscope {
region: implied_output_region,
};
ast_ty_to_ty(this, &rb, &*decl.output)
}
None => {
// All regions must be explicitly specified in the output
// if the lifetime elision rules do not apply. This saves
// the user from potentially-confusing errors.
let rb = ExplicitRscope;
ast_ty_to_ty(this, &rb, &*decl.output)
}
}
}
};

(ty::BareFnTy {
Expand Down
16 changes: 15 additions & 1 deletion src/librustc/middle/typeck/rscope.rs
Expand Up @@ -64,10 +64,24 @@ impl RegionScope for BindingRscope {
fn anon_regions(&self,
_: Span,
count: uint)
-> Result<Vec<ty::Region> , ()> {
-> Result<Vec<ty::Region>, ()> {
let idx = self.anon_bindings.get();
self.anon_bindings.set(idx + count);
Ok(Vec::from_fn(count, |i| ty::ReLateBound(self.binder_id,
ty::BrAnon(idx + i))))
}
}

/// A scope in which we generate one specific region. This occurs after the
/// `->` (i.e. in the return type) of function signatures.
pub struct ImpliedSingleRscope {
pub region: ty::Region,
}

impl RegionScope for ImpliedSingleRscope {
fn anon_regions(&self, _: Span, count: uint)
-> Result<Vec<ty::Region>,()> {
Ok(Vec::from_elem(count, self.region.clone()))
}
}

2 changes: 1 addition & 1 deletion src/librustrt/local_data.rs
Expand Up @@ -94,7 +94,7 @@ pub type Map = Vec<Option<(*const u8, TLSValue, uint)>>;
type TLSValue = Box<LocalData + Send>;

// Gets the map from the runtime. Lazily initialises if not done so already.
unsafe fn get_local_map() -> Option<&mut Map> {
unsafe fn get_local_map<'a>() -> Option<&'a mut Map> {
if !Local::exists(None::<Task>) { return None }

let task: *mut Task = Local::unsafe_borrow();
Expand Down
2 changes: 1 addition & 1 deletion src/librustrt/rtio.rs
Expand Up @@ -134,7 +134,7 @@ impl<'a> Drop for LocalIo<'a> {
impl<'a> LocalIo<'a> {
/// Returns the local I/O: either the local scheduler's I/O services or
/// the native I/O services.
pub fn borrow() -> Option<LocalIo> {
pub fn borrow() -> Option<LocalIo<'a>> {
// FIXME(#11053): bad
//
// This is currently very unsafely implemented. We don't actually
Expand Down
2 changes: 1 addition & 1 deletion src/librustuv/file.rs
Expand Up @@ -469,7 +469,7 @@ mod test {
use super::super::Loop;
use super::super::local_loop;

fn l() -> &mut Loop { &mut local_loop().loop_ }
fn l() -> &'static mut Loop { &mut local_loop().loop_ }

#[test]
fn file_test_full_simple_sync() {
Expand Down
6 changes: 3 additions & 3 deletions src/test/auxiliary/overloaded_autoderef_xc.rs
Expand Up @@ -15,17 +15,17 @@ struct DerefWithHelper<H, T> {
}

trait Helper<T> {
fn helper_borrow<'a>(&'a self) -> &'a T;
fn helper_borrow(&self) -> &T;
}

impl<T> Helper<T> for Option<T> {
fn helper_borrow<'a>(&'a self) -> &'a T {
fn helper_borrow(&self) -> &T {
self.as_ref().unwrap()
}
}

impl<T, H: Helper<T>> Deref<T> for DerefWithHelper<H, T> {
fn deref<'a>(&'a self) -> &'a T {
fn deref(&self) -> &T {
self.helper.helper_borrow()
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/test/bench/shootout-k-nucleotide.rs
Expand Up @@ -156,7 +156,7 @@ impl Table {
}
}

fn iter<'a>(&'a self) -> Items<'a> {
fn iter(&self) -> Items {
Items { cur: None, items: self.items.iter() }
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/test/compile-fail/borrowck-borrow-from-temporary.rs
Expand Up @@ -13,7 +13,7 @@

struct Foo(int);

fn foo() -> &int {
fn foo<'a>() -> &'a int {
let &Foo(ref x) = &Foo(3); //~ ERROR borrowed value does not live long enough
x
}
Expand Down
2 changes: 1 addition & 1 deletion src/test/compile-fail/borrowck-borrow-mut-object-twice.rs
Expand Up @@ -12,7 +12,7 @@
// other `&mut` pointers.

trait Foo {
fn f1<'a>(&'a mut self) -> &'a ();
fn f1(&mut self) -> &();
fn f2(&mut self);
}

Expand Down

1 comment on commit 6f99a27

@pcwalton
Copy link
Owner Author

Choose a reason for hiding this comment

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

r=nick29581

Please sign in to comment.