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
.
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())
}