Search code examples
rustxcb

Embedding variables with lifetimes in struct


I'm a beginner in Rust and I want to make a simple application to render fractals like Mandelbrot. The fractals are rendered into a X11-window. The X11-window is made with the xcb crate (version 0.7.4).

I want to encapsulate everything that is needed for the window in a struct.

extern crate xcb;
use xcb::base::*;

struct FbWindow {
    conn: Connection,
    window: u32,
    gc: u32,
    width: u16,
    height: u16,
    fb: Vec<u8>
}

In my new function for the struct I need a setup object from the connection which somehow has the same lifetime as the connection object.

impl FbWindow {
    fn new(width: u16, height: u16) -> FbWindow 
    {
        let (conn, screen_nr) = Connection::connect(None).unwrap();
        let setup = conn.get_setup();
        let screen = setup.roots().nth(screen_nr as usize).unwrap();
        let root = screen.root();

        /* Create GC - graphics context */
        let gc = conn.generate_id();
        let gc_values = [
            (xcb::GC_FOREGROUND, screen.black_pixel()),
            (xcb::GC_GRAPHICS_EXPOSURES, 0)
        ];
        xcb::create_gc(&conn, gc, root, &gc_values);

        /* Create window */
        let window = conn.generate_id();
        let window_values = [
            (xcb::CW_BACK_PIXEL, screen.black_pixel()),
            (xcb::CW_EVENT_MASK, xcb::EVENT_MASK_EXPOSURE | xcb::EVENT_MASK_KEY_PRESS)
        ];
        xcb::create_window(&conn, xcb::COPY_FROM_PARENT as u8, window, root,
            0, 0, width, height, 1, xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
            screen.root_visual(), &window_values
        );
        xcb::map_window(&conn, window);

        /* Create the framebuffer */
        let mut fb : Vec<u8> = vec![0; (width as usize) * (height as usize) * 4];

        FbWindow {
            conn: conn, 
            window: window,
            gc: gc,
            width: width,
            height: height,
            fb: fb
        }
    }
}

The compiler doesn't let me move the connection object into the structure object that should be returned by new. I also tried with adding setup into the structure but it doesn't help. The compiler gives the following error with code from above:

src/main.rs:46:19: 46:23 error: cannot move out of `conn` because it is borrowed [E0505]
src/main.rs:46             conn: conn, 
                                 ^~~~
src/main.rs:18:21: 18:25 note: borrow of `conn` occurs here
src/main.rs:18         let setup = conn.get_setup();
                                   ^~~~

Looking up the documentation about the type of setup, reveals

type Setup<'a> = StructPtr<'a, xcb_setup_t>;

I'm really new to rust and the concept of lifetimes and it's still confusing to me, but as far as I understand setup has the same lifetime as conn and the compiler refuses to move because of the borrowing in setup.

How do I get the code working as intended?

Edit: The code is based of the examples from the crate repository Edit2: Full source code for new.


Solution

  • We've all scratched our heads about this. For the most part, the compiler tells you what is wrong.

    At line 18 you are borrowing conn:

    let setup = conn.get_setup();
    

    Most methods take &self or &mut self as their first argument, thus borrowing the object they are called on. If the method doesn't return anything, the borrow will end at the end of it's scope. That isn't the case here as setup is using a lifetime (the 'a in Setup<'a>), which will extend the life of the borrow. This usually doesn't get in your way since you can have as many immutable borrows as you want, but as long as you have at least one, the owned variable cannot be moved.

    So! As long as setup exists, the compiler will not allow you to move conn. To fix that, you need to make sure that setup "dies" before you create the struct. A simple way to do it, is to wrap it around in a block, like so:

    fn new(width: u16, height: u16) -> FbWindow 
    {
        let (conn, screen_nr) = Connection::connect(None).unwrap();
    
        // because of block scoping, you might want to declare variables here
        let gc: u32;
        ...
    
        {
            // Borrowing `conn` here...
            let setup = conn.get_setup();
            let screen = setup.roots().nth(screen_nr as usize).unwrap();
            ...
    
            // Assign to the on the outer scope
            gc = ...;
    
            // All variables declared within this block will drop here,
        }
    
        // `conn` is not borrowed anymore!
    
        FbWindow {
            conn: conn,
            window: window,
            gc: gc,
            width: width,
            height: height,
            fb: fb
        }
    }
    

    Alternatively, instead of declaring uninitialized variables you can exploit the fact that block is an expression in Rust and resolves to the last line in it. You can pack things in a tuple and deconstruct it with pattern matching:

    let (window, gc, fb) = {
        ...
    
        // no semicolon here
        (0, 0, Vec::new())
    }