Skip to content

Lifetime bound "forgotten" in a function #10703

@honzasp

Description

@honzasp

Hi,

if a struct is bound by a lifetime which is not used in the body of the struct, this bound seems to be excluded from typechecking.

Consider following functions (not valid Rust, because the function and struct bodies are omitted):

struct BananaTree;
struct Banana<'a>;

fn create_banana<'a>(tree: &'a BananaTree) -> Banana<'a>;
fn banana_calories<'a>(banana: Banana<'a>) -> int;
fn transform_banana<'a>(banana: Banana<'a>) -> Banana<'a>;

Bananas are created from BananaTrees and their lifetime must not exceed the lifetime of their tree. banana_calories expects the passed banana to be alive, and transform_banana creates a banana from another one, preserving the lifetime information.

The lifetimes should cause the following function to be rejected by the compiler, because the result of transform_banana has lifetime bound to the lifetime of tree, which is tied to the enclosing block. leaked_banana and the following call to banana_calories should then be invalid.

fn main() {
  let leaked_banana = {
    let tree = BananaTree { ... };
    let banana = create_banana(&tree);
    transform_banana(banana)
  };
  println!("{}", banana_calories(leaked_banana));
}

If the bananas and functions are defined like this:

struct BananaTree { calories: int, }
struct Banana<'a> { calories: &'a int }

fn create_banana<'a>(tree: &'a BananaTree) -> Banana<'a> {
  Banana { calories: &tree.calories }
}

fn banana_calories<'a>(banana: Banana<'a>) -> int {
  *banana.calories
}

fn transform_banana<'a>(banana: Banana<'a>) -> Banana<'a> {
  banana
}

The lifetime bound 'a in Banana<'a> is used in the field calories, forcing the borrow checks and making the invalid use shown above fail with appropriate error message (tree does not live long enough):

fn main() {
  let leaked_banana = {
    let tree = BananaTree { calories: 105 };
    let banana = create_banana(&tree);
    transform_banana(banana)
  };
  println!("{}", banana_calories(leaked_banana));
}

On the other hand, if the implementation is like:

struct BananaTree;
struct Banana<'a> { calories: int }

fn create_banana<'a>(tree: &'a BananaTree) -> Banana<'a> {
  Banana { calories: 105 }
}

fn banana_calories<'a>(banana: Banana<'a>) -> int {
  banana.calories
}

fn transform_banana<'a>(banana: Banana<'a>) -> Banana<'a> {
  banana
}

Then the lifetime bound on Banana<'a> is not used in the member fields at all, but it still should be tied to the lifetime of the BananaTree passed to create_banana and it should limit the use of the created banana. It doesn't work like that, though, because the following function compiles:

fn main() {
  let leaked_banana = {
    let tree = BananaTree;
    let banana = create_banana(&tree);
    transform_banana(banana)
  };
  println!("{}", banana_calories(leaked_banana));
}

The lifetime of banana from create_banana is clearly not tied to lifetime of tree, because the compiler allows us to use the banana when its mother tree went out of scope.

This is in fact safe, because Banana didn't contain any reference to the BananaTree. The point is that I would like to use Rust's lifetimes in a C library bindings, where the Banana values live as long as their respective BananaTrees (as a matter of fact, the library is LLVM, banana trees are modules/functions and bananas are values).

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-lifetimesArea: Lifetimes / regions

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions