1

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.

fsasm
  • 531
  • 1
  • 8
  • 23
  • Probably a duplicate of http://stackoverflow.com/q/32300132 – Shepmaster Jul 19 '16 at 21:23
  • @malbarbo I added a link to the example – fsasm Jul 19 '16 at 21:28
  • @fsasm it is expected that the question you ask *here* be a [mcve]. Providing a offsite link to the code with the real issue won't help anyone else in the future and could be reason to close this question as it doesn't provide enough information to reproduce. – Shepmaster Jul 20 '16 at 01:22

1 Answers1

2

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())
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Maciej
  • 136
  • 3
  • I tried with the blocks as you suggested but I still get the same error messages. – fsasm Jul 20 '16 at 07:40
  • I think that your solution doesn't work because `setup` is bound to the lifetime of `conn` and therefore the borrow exists during the complete lifetime of `conn`. – fsasm Jul 20 '16 at 07:48
  • Can you paste the modified code somewhere? `setup` is indeed bound to the lifetime of `conn`, the borrow should end when `setup` runs out of scope. From what I see all the other values on the struct are `Copy` so, nothing else can borrow `conn`. – Maciej Jul 20 '16 at 12:52
  • Change the two lines with `setup` and `screen` to this: `let screen = {let setup = conn.get_setup(); setup.roots().nth(screen_nr as usize).unwrap()};` – fsasm Jul 20 '16 at 13:19
  • Yeah, that won't do because you will still have `screen` and `setup` available in the same block as `conn`. The point is that you do whatever you need to do with them _inside_ the block and only move the actual values you need outside (`window`, `gc` and `fb`). – Maciej Jul 20 '16 at 14:23
  • Thanks. Now it works, although `fb` is only a vector and therefore only `window` and `gc` needs to be moved outside the block. I didn't saw that the lifetime also affects `screen` and `root` as the compiler only complained about `setup`. – fsasm Jul 20 '16 at 14:43