Problem return an Arc type from a new function

Hi,

I am following through some vulkan tutorials and try to an implement a struct new function creating the struct elements. Intent is to of course create reusable code.

se std::sync::Arc;

use vulkano::device::{Device, DeviceExtensions, Features, Queue};
use vulkano::instance::InstanceCreationError;
use vulkano::instance::{Instance, PhysicalDevice, QueueFamily};

struct Halstate<'a> {
    instance:  Box<Arc<Instance>>,
    phy_device: PhysicalDevice<'a>,
    queue_family: QueueFamily<'a>,
    logical_device: Arc<Device>,
    queue: Arc<Queue>,
}

impl<'a> Halstate<'a> {
    fn new() -> Halstate<'a> {
        let required_extensions = vulkano_win::required_extensions();

        // setup vulkan with required_extensions ; type InstanceExtension
        // This sets the following to true ; all else are false
        // khr_surface
        // khr_win32_surface
        // khr_get_physical_device_properties
        // khr_get_surface_capabilities2

        let instance = match Instance::new(None, &required_extensions, None) {
            Ok(instance) => Box::new(instance),
            Err(err) => match err {
                InstanceCreationError::IncompatibleDriver => panic!("Incompatible Driver"),
                _ => panic!("unknown error"),
            },
        };

        // List the available devices

        for phy_dev in PhysicalDevice::enumerate(&instance) {
            println!("Using device: {} (type:{:?})", phy_dev.name(), phy_dev.ty());
        }

        let phy_device = PhysicalDevice::enumerate(&instance)
            .next()
            .expect("no device available");

        // for the physical device list the available queue families and give the number of queues
        // they support

        for qf in phy_device.queue_families() {
            println!("Found a queue family with {:?} queue(s)", qf.queues_count());
        }

        // Choose first queue family that supports graphics

        let qf = phy_device
            .queue_families()
            .find(|&q| q.supports_graphics())
            .expect("couldn't find a graphical queue family");

        // open a logical device and acquire a list of its queues

        let (logical_device, mut queues) = {
            Device::new(
                phy_device,
                &Features::none(),
                &DeviceExtensions::none(),
                [(qf, 0.5)].iter().cloned(),
            )
            .expect("failed to create device")
        };

        println!("{:?}", logical_device);

        // use first available queue for the device

        let queue = queues.next().unwrap();

        println!("{:?}", queue);

        Halstate {
            instance: instance,
            phy_device,
            queue_family: qf,
            logical_device,
            queue,
        }

        // Surface supports queue_family
        // Instance dropped here; vk.DestroyInstance
    }
}

fn main() {
    Halstate::new();
}

//#[allow(dead_code)]

from this I get an error particularly about returning a local variable.

error[E0515]: cannot return value referencing local variable `instance`
  --> init-command-buffer\src\main.rs:78:9
   |
40 |           let phy_device = PhysicalDevice::enumerate(&instance)
   |                                                      --------- `instance` is borrowed here
...
78 | /         Halstate {
79 | |             instance: instance,
80 | |             phy_device,
81 | |             queue_family: qf,
82 | |             logical_device,
83 | |             queue,
84 | |         }
   | |_________^ returns a value referencing data owned by the current function

error[E0505]: cannot move out of `instance` because it is borrowed
  --> init-command-buffer\src\main.rs:79:23
   |
15 |   impl<'a> Halstate<'a> {
   |        -- lifetime `'a` defined here
...
40 |           let phy_device = PhysicalDevice::enumerate(&instance)
   |                                                      --------- borrow of `instance` occurs here
...
78 | /         Halstate {
79 | |             instance: instance,
   | |                       ^^^^^^^^ move out of `instance` occurs here
80 | |             phy_device,
81 | |             queue_family: qf,
82 | |             logical_device,
83 | |             queue,
84 | |         }
   | |_________- returning this value requires that `instance` is borrowed for `'a`

I think I understand the problem but I don't understand the solution. I thought adding Box and lifetimes for instance would help but so far no luck.
Any ideas?

Thanks,
Frank

I'm on my phone right now, so this is a short reply. I would get rid of this Box around the Arc as it serves no purpose. For the physical device and queue family the easiest approach seems to be just store there index/id. You can always get them back using the index and id if you need to access them. This way you get rid of the lifetime parameter of the struct.

1 Like

Looking further into the API of that crate, it seems to be that case that you can access the device and the queue family through the queue, and access the physical device and the instance from the device. So wouldn't you just need the Arc<Queue>?

1 Like

Hi , I don't doubt you are right in your observation but the question about Instance still remains in one sense. How do I semantically extend the lifetime of Instance to save it as element in a returned struct? It does remain an interesting question in itself. I originally I thought Boxed types were saved on the heap, even the inner type. Is it because Arc itself cannot have the Copy trait?

Thanks,
Frank

Instance lives as long as you maintain ownership of it, so you can extend its lifetime indefinitely by any means you like: just don't drop it.

The real problem here is that you can't store the Instance in the same struct as any data that borrows from it, because that creates a self-borrow. If a struct owns its members, then it is always allowed to manipulate and move them. If it borrows them, it is not allowed to manipulate or move them. So a self-borrow creates a situation where a struct doesn't properly own its own data, and though there are ways around that, you really want to avoid constructing self-borrows in Rust.

The simplest solution to this problem is to pass the Instance into your HalState constructor and don't store the Instance within it.

I'm not sure why this is relevant, unless you were expecting that by Boxing (or Arcing) Instance you should be able to borrow from it in HalState. Box and Arc are both owning pointers, and it's the ownership that is important, not the location of the data. Borrowing from Instance means those borrowing pointers become invalid if anything that owns Instance would move, including the Box (or Arc, HalState, or stack frame) that contains it.

Basically, if HalState owns Instance, then it is free to replace that instance with another. And the Instance may also internally own some data through its own Boxes. So by swapping Instances, you might drop data on the heap. This is a huge problem if something is simultaneously borrowing from Instance, because you don't know if its pointing to the Instance directly or to something else that it owns.

Arc doesn't change much, because it's just a Box that doesn't deallocate until all pointers are gone. In order to get shared ownership, you have to entirely forego borrows, which means using an API built around Arc. So if an API creates borrows you can't really use Arc to to get around the lifetime requirements.

2 Likes

Skysch and steffahn,

Thanks for your help. I changed the HalState struct to account for the borrow problems that conflict with borrowing in the function calls.

struct Halstate {
    //instance: found by let ints = PhysicalDevice::instance(&phy_device);
    //phy_device: PhysicalDevice<'a>, let phy_device = Device::physical_device(&logical_device);
    //queue_family: QueueFamily<'a>, Device::active_queue_families(logical_device)
    logical_device: Arc<Device>,
    queue: Arc<Queue>,
}

I arrived at this problem

  1. by using an out of date example, GitHub - rust-tutorials/learn-gfx-hal: Step by step tutorials for gfx-hal, This summer while trying to learn rust I also tried to update this tutorial as it fails to build and is quite out of date. This was not something to do while initially learning rust. :roll_eyes:
  2. the tutorial did have a hal-state struct to yes a different but similiar api. That was the inspiration for me to create the original struct above.
  3. I am now working through the examples at LunarXchange using vulkano. It is a good exercise for my level at this point.

Sometime I might try to go back to upgrading the learning_gfx_hal tutorial.

Thanks for the help and hints.

Frank

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.