Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Advice on passing Vec<T> by mutable reference from Rust to a C++ function (fill the container on the C++ side) #539

Closed
ph633x opened this issue Dec 3, 2020 · 3 comments
Labels

Comments

@ph633x
Copy link

ph633x commented Dec 3, 2020

First of all, thanks for this amazing project. Having a safe bridge between Rust and C++ helps me to keep my anxiety under control :-).

I would like to hear your advice on whether it is possible, and if yes, the best way, to pass a Vec<u8> from Rust onto a C++ function expecting a mutable reference to std::vector<uint8_t>, where it would be then populated with some values based on some computation.

So, the ownership would be kept on Rust's side, but the mutable reference passed to C++ would be resized and used to populate the container.

An example looks like the following:

// api.h
class FakeStore {
  // Given a key, populates value, possibly resizing it
  bool get(std::string const& key, std::vector<uint8_t>& value);
};

// api.cpp
bool FakeStore(std::string const& key, std::vector<uint8_t>& value) {
  if (key == "abc") {
    value = {1, 2, 3};
    return true;
   } else {
     return false;
   }
}

And "expected" Rust side:

fn main() {
   let value_buf = Vec::new();
   // make_unique FakeStore, key, etc...
   let ok = store.get(key, &mut value_buf);
   // use value_buf
}

My attempt to bridge:

mod ffi {
  unsafe extern C++ {
   include("api.h");

   type FakeStore;
   fn get(self: &mut FakeStore, key: &CxxString, value: &must CxxVector<u8>) -> bool;
  }
}

But I couldn't get it to work. First it complained that I had to put the mutable ref into a Pin, but then I cannot Pin a CxxVector as it has a PhantomPinned and there I got stuck. So, I am probably trying some nonsensical approach 😅. Maybe this is not allowed at all, given that in general C++ could store the reference behind the scenes and keep it around even after the owning side (Rust) got dropped...

Is it possible to define a bridge for this API to work? Or would I need to change the C++ side to return an unique_ptr wrapping the vector and then having an UniquePtr<CxxVector> on the Rust side? Or is there another way?

Side-questions:

  1. Would it change if I had an Vec<T> where T is some generic type instead of u8?
  2. Given that I cannot (or can I?) return FakeStore by value, I cannot call its ctor, instead I have to wrap it in an UniquePtr, right? The same by-value reasoning holds for vector<T> that would also need to be returned boxed into a pointer such as unique_ptr, right?
  3. Given that get takes FakeStore (this/self) by mutable reference, what are the implications on the bridge? I couldn't get self: &mut FakeStore to work, where FakeStore was treated as an opaque type type FakeStore in the unsafe extern "C++ section.

Thank you very much for the assistance and please let me know if I should provide more info.

Regards.

@dtolnay
Copy link
Owner

dtolnay commented Dec 4, 2020

For the FakeStore class in your question, you would do something like:

// api.h

#pragma once
#include <memory>
#include <string>
#include <vector>

class FakeStore {
public:
  bool get(std::string const& key, std::vector<uint8_t>& value);
};

std::unique_ptr<FakeStore> new_fake_store();
std::unique_ptr<std::vector<uint8_t>> new_value_buf();
// main.rs

use cxx::let_cxx_string;

#[cxx::bridge]
mod ffi {
    unsafe extern "C++" {
        include!("example/include/api.h");

        type FakeStore;
        fn get(self: Pin<&mut FakeStore>, key: &CxxString, value: Pin<&mut CxxVector<u8>>) -> bool;

        fn new_fake_store() -> UniquePtr<FakeStore>;
        fn new_value_buf() -> UniquePtr<CxxVector<u8>>;
    }
}

fn main() {
    let mut store = ffi::new_fake_store();
    let mut value_buf = ffi::new_value_buf();
    let_cxx_string!(key = "abc");
    let ok = store.pin_mut().get(&key, value_buf.pin_mut());
    println!("{} {:?}", ok, value_buf.as_slice());
}

Alternatively you could use a Rust Vec somewhat more simply.

// api.h

#pragma once
#include "rust/cxx.h"
#include <memory>

class FakeStore {
public:
  bool get(rust::Str key, rust::Vec<uint8_t>& value);
};

std::unique_ptr<FakeStore> new_fake_store();
// api.cpp

#include "example/include/api.h"

bool FakeStore::get(rust::Str key, rust::Vec<uint8_t>& value) {
  if (key == "abc") {
    value = {1, 2, 3};
    return true;
  } else {
    return false;
  }
}

std::unique_ptr<FakeStore> new_fake_store() { return std::make_unique<FakeStore>(); }
// main.rs

#[cxx::bridge]
mod ffi {
    unsafe extern "C++" {
        include!("example/include/api.h");

        type FakeStore;
        fn get(self: Pin<&mut FakeStore>, key: &str, value: &mut Vec<u8>) -> bool;

        fn new_fake_store() -> UniquePtr<FakeStore>;
    }
}

fn main() {
    let mut store = ffi::new_fake_store();
    let mut value_buf = Vec::new();
    let ok = store.pin_mut().get("abc", &mut value_buf);
    println!("{} {:?}", ok, value_buf);
}

@dtolnay
Copy link
Owner

dtolnay commented Dec 4, 2020

Given that I cannot (or can I?) return FakeStore by value, I cannot call its ctor, instead I have to wrap it in an UniquePtr, right?

This is tracked in #280.

@ph633x
Copy link
Author

ph633x commented Dec 9, 2020

Thank you for the explanation.

I will give it a run.

@ph633x ph633x closed this as completed Dec 9, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants