5

I would like to branch and decide on a Trait implementation to use within a function at runtime (see poly_read in the code sample below). The trait object is constructed inside of the branch arms of an if expression and only needs to live for the life of poly_read yet I need to Box it because the trait can't be borrowed from within the expression arm, up to the binding that I'm attempting to assign it to.

I understand logically why the borrow ends too early, but it seems like the borrow checker should be able to extend the borrow up to the surrounding scope when the if expression's value is bound. I realize this is probably a naive notion, but I'd like to understand more about why it's not possible.

I'm a bit unhappy with the solution I have now because it requires a heap allocation even though I feel like I shouldn't need one, since I only hold onto the box for the life of the function. I suppose this is because we don't know the size of reader that would be required on the stack until the branch is taken, but couldn't it just be represented as a union in the compiler, since we at least know the maximum size.

As an aside, I actually don't know how valid my concern about the Box being heap allocated is to begin with. In general how expensive is boxing the value?

#![feature(io)]
#![feature(path)]

const BYTES: &'static [u8] = &[1u8, 2, 3, 4, 5];
const PATH: &'static str = "/usr/share/dict/words";

use std::old_io::{File, Reader, BufReader};


fn read(r: &mut Reader) {
    let some_bytes = r.read_exact(5).unwrap();
    assert!(some_bytes.len() == 5);
    println!("{:?}", some_bytes);
}

fn poly_read(from_file: bool) {
    // Is there any way to extend the lifetime of the ``&mut Reader`` in these branch arms without
    // boxing them as I'm doing now. It seems wasteful to do a heap allocation when the actual
    // borrow only needs to happen for body of poly_read?
    let mut reader = if from_file {
        Box::new(File::open(&Path::new(PATH)).unwrap()) as Box<Reader>
        // Would like to say:
        // File::open(&Path::new(FILE)).unwrap() as &mut Reader
    } else {
        Box::new(BufReader::new(BYTES)) as Box<Reader>
        // Would like to say:
        // BufReader::new(BYTES) as &mut Reader
    };
    // It feels like I'd like the lifetime of values returned from if expressions to be of the
    // surrounding scope, rather than the branch arms.
    read(&mut reader);
}

fn main() {
    poly_read(true);
    poly_read(false);
}
JakeK
  • 61
  • 2
  • possible duplicate of [Are polymorphic variables allowed?](http://stackoverflow.com/questions/28219519/are-polymorphic-variables-allowed) – Shepmaster Feb 01 '15 at 15:31
  • 1
    Specifically [this answer](http://stackoverflow.com/a/28220053/155423) to that question. – Shepmaster Feb 01 '15 at 15:32
  • No problem, and welcome to Stack Overflow! Make sure to upvote questions and answers that you find useful. This helps other people (and future you) find high-quality answers. – Shepmaster Feb 01 '15 at 15:48

1 Answers1

2

As pointed out by @Shepmaster, there is a way to do this similar to this answer from a previous question.

The way to solve this is to predeclare the two necessary variables: a File, and a BufReader:

fn poly_read(from_file: bool) {
    // These two variables are predeclared so that they are in scope as
    // long as `reader` is
    let mut file_reader;
    let mut buf_reader;

    let mut reader = if from_file {
        file_reader = File::open(&Path::new(PATH)).unwrap();
        &mut file_reader as &mut Reader
    } else {
        buf_reader = BufReader::new(BYTES);
        &mut buf_reader as &mut Reader
    };

    read(&mut reader);
}

Also see this code on the Rust playpen..

Community
  • 1
  • 1
Wendell
  • 21
  • 2