How can I safely share objects between Rust and C++? - rust

One way to construct and destruct C++ objects from Rust is to call the constructor and return an int64_t pointer to Rust. Then, Rust can call methods on the object by passing the int64_t which will be cast to the pointer again.
void do_something(int64_t pointer, char* methodName, ...) {
//cast pointer and call method here
}
However, this is extremely unsafe. Instead I tend to store the pointer into a map and pass the map key to Rust, so it can call C++ back:
void do_something(int id, char* methodName, ...) {
//retrieve pointer from id and call method on it
}
Now, imagine I create, from Rust, a C++ object that calls Rust back. I could do the same: give C++ an int64_t and then C++ calls Rust:
#[no_mangle]
pub fn do_somethind(pointer: i64, method_name: &CString, ...) {
}
but that's also insecure. Instead I'd do something similar as C++, using an id:
#[no_mangle]
pub fn do_something(id: u32, method_name: &CString, ...) {
//search id in map and get object
//execute method on the object
}
However, this isn't possible, as Rust does not have support for static variables like a map. And Rust's lazy_static is immutable.
The only way to do safe calls from C++ back to Rust is to pass the address of something static (the function do_something) so calling it will always point to something concrete. Passing pointers is insecure as it could stop existing. However there should be a way for this function to maintain a map of created objects and ids.
So, how to safely call Rust object functions from C++? (for a Rust program, not a C++ program)

Pointers or Handles
Ultimately, this is about object identity: you need to pass something which allows to identify one instance of an object.
The simplest interface is to return a Pointer. It is the most performant interface, albeit requires trust between the parties, and a clear ownership.
When a Pointer is not suitable, the fallback is to use a Handle. This is, for example, typically what kernels do: a file descriptor, in Linux, is just an int.
Handles do not preclude strong typing.
C and Linux are poor examples, here. Just because a Handle is, often, an integral ID does not preclude encapsulating said integer into a strong type.
For example, you could struct FileDescriptor(i32); to represent a file descriptor handed over from Linux.
Handles do not preclude strongly typed functions.
Similarly, just because you have a Handle does not mean that you have a single syscall interface where the name of the function must be passed by ID (or worse string) and an unknown/untyped soup of arguments follow.
You can perfectly, and really should, use strongly typed functions:
int read(FileDescriptor fd, std::byte* buffer, std::size_t size);
Handles are complicated.
Handles are, to a degree, more complicated than pointers.
First of all, handles are meaningless without some repository: 33 has no intrinsic meaning, it is just a key to look-up the real instance.
The repository need not be a singleton. It can perfectly be passed along in the function call.
The repository should likely be thread-safe and re-entrant.
There may be data-races between usage and deletion of a handle.
The latter point is maybe the most surprising, and means that care must be taken when using the repository: accesses to the underlying values must also be thread-safe, and re-entrant.
(Non thread-safe or non re-entrant underlying values leave you open to Undefined Behavior)
Use Pointers.
In general, my recommendation is to use Pointers.
While Handles may feel safer, implementing a correct system is much more complicated than it looks. Furthermore, Handles do not intrinsically solve ownership issues. Instead of Undefined Behavior, you'll get Null Pointer Dangling Handle Exceptions... and have to reinvent the tooling to track them down.
If you cannot solve the ownership issues with Pointers, you are unlikely to solve them with Handles.

Related

Why does pthread_exit use void*?

I recently started using posix threads and the choice of argument types in the standard made me curious. I haven't been able to asnwer the question of why does pthread_exit use void* instead of int for the returning status of the thread? (the same as exit does).
The only advantage I see is that it lets the programmers define the status how they want (e.g. return a pointer to a complex structure), but I doubt it is widely used like this.
It seems that in most cases this choice has more overhead because of necessary casting.
This isn't just for status, it's the return value of the thread. Using a pointer allows the thread to return a pointer to a dynamically-allocated array or structure.
You can't really compare it with the exit() parameter, because that's for sending status to the operating system. This is intentionally very simple to allow portability with many OSes.
The only advantage I see is that it lets the programmers define the status how they want (e.g. return a pointer to a complex structure), but I doubt it is widely used like this.
Indeed, that's the reason. And it's probably not used that widely (e.g. you can communicate values via other means such as a pointer passed to thread function, global var with synchronisation, etc). But if you were to have a it like void pthread_exit(int);, the it takes away the ability to return pointers. So void pthread_exit(void*); is a more flexible design.
It seems that in most cases this choice has more overhead because of necessary casting.
In most cases, it's not used at all as the common way is to return nothing i.e. pthread_exit(NULL);. So it only matters when returning pointers (to structs and such) and in those cases a conversion to void * isn't necessary as any pointer type can be converted to void * without an explicit cast.

How to implement long-lived variables/state in a library?

I understand that the preferred way to implement something like a global/instance/module variable in Rust is to create said variable in main() or other common entry point and then pass it down to whoever needs it.
It also seems possible to use a lazy_static for an immutable variable, or it can be combined with a mutex to implement a mutable one.
In my case, I am using Rust to create a .so with bindings to Python and I need to have a large amount of mutable state stored within the Rust library (in response to many different function calls invoked by the Python application).
What is the preferred way to store that state?
Is it only via the mutable lazy_static approach since I have no main() (or more generally, any function which does not terminate between function calls from Python), or is there another way to do it?
Bundle it
In general, and absent other requirements, the answer is to bundle your state in some object and hand it over to the client. A popular name is Context.
Then, the client should have to pass the object around in each function call that requires it:
Either by defining the functionality as methods on the object.
Or by requiring the object as parameter of the functions/methods.
This gives full control to the client.
The client may end up creating a global for it, or may actually appreciate the flexibility of being able to juggle multiple instances.
Note: There is no need to provide any access to the inner state of the object; all the client needs is a handle (ref-counted, in Python) to control the lifetime and decide when to use which handle. In C, this would be a void*.
Exceptions
There are cases, such as a cache, where the functionality is not impacted, only the performance.
In this case, while the flexibility could be appreciated, it may be more of a burden than anything. A global, or thread-local, would then make sense.
I'd be tempted to dip into unsafe code here. You cannot use non-static lifetimes, as the lifetime of your state would be determined by the Python code, which Rust can't see. On the other hand, 'static state has other problems:
It necessarily persists until the end of the program, which means there's no way of recovering memory you're no longer using.
'static variables are essentially singletons, making it very difficult to write an application that makes multiple independent usages of your library.
I would go with a solution similar to what #Matthieu M. suggests, but instead of passing the entire data structure back and forth over the interface, allocate it on the heap, unsafely, and then pass some sort of handle (i.e. pointer) back and forth.
You would probably want to write a cleanup function, and document your library to compel users to call the cleanup function when they're done using a particular handle. Effectively, you're explicitly delegating the management of the lifecycle of the data to the calling code.
With this model, if desired, an application could create, use, and cleanup multiple datasets (each represented by their own handle) concurrently and independently. If an application "forgets" to cleanup a handle when finished, you have a memory leak, but that's no worse than storing the data in a 'static variable.
There may be helper crates and libraries to assist with doing this sort of thing. I'm not familiar enough with rust to know.

std::string, const, and thread safety

This question has been discussed but I haven't seen a set answer yet. It's partially discussed here:
https://www.justsoftwaresolutions.co.uk/cplusplus/const-and-thread-safety.html
But the answer still isn't totally clear with me.
If you define std::string const kstrValue = "Value" is kstrValue inherently thread-safe?
What my research has indicated is that it is thread-safe as long as you don't call std library functions that mutate the string.
Is this true?
You can't call any functions that modify that string, std:: or otherwise. What the article is saying, is write your classes like std::string (or a hypothetical int class).
By ensuring that methods that don't modify the class are marked const, you can have const Foo objects freely shared among threads, safe in the knowledge that there can be no data races, because there are no modifications.
It is slightly more subtle with const Foo & references. You don't know if the underlying object really is const, or that const was added to the reference and somewhere else modifications can occur. Access to such objects still needs to be synchronised between threads.
Yes, it is.
[res.on.data.races]/3:
A C++ standard library function shall not directly or indirectly modify objects ([intro.multithread]) accessible by threads other than the current thread unless the objects are accessed directly or indirectly via the function's non-const arguments, including this.

Are There Any Hidden Costs to Passing Around a Struct With a Single Reference?

I was recently reading this article on structs and classes in D, and at one point the author comments that
...this is a perfect candidate for a struct. The reason is that it contains only one member, a pointer to an ALLEGRO_CONFIG. This means I can pass it around by value without care, as it's only the size of a pointer.
This got me thinking; is that really the case? I can think of a few situations in which believing you're passing a struct around "for free" could have some hidden gotchas.
Consider the following code:
struct S
{
int* pointer;
}
void doStuff(S ptrStruct)
{
// Some code here
}
int n = 123;
auto s = S(&n);
doStuff(s);
When s is passed to doStuff(), is a single pointer (wrapped in a struct) really all that's being passed to the function? Off the top of my head, it seems that any pointers to member functions would also be passed, as well as the struct's type information.
This wouldn't be an issue with classes, of course, since they're always reference types, but a struct's pass by value semantics suggests to me that any extra "hidden" data such as described above would be passed to the function along with the struct's pointer to int. This could lead to a programmer thinking that they're passing around an (assuming a 64-bit machine) 8-byte pointer, when they're actually passing around an 8-byte pointer, plus several other 8-byte pointers to functions, plus however many bytes an object's typeinfo is. The unwary programmer is then allocating far more data on the stack than was intended.
Am I chasing shadows here, or is this a valid concern when passing a struct with a single reference, and thinking that you're getting a struct that is a pseudo reference type? Is there some mechanism in D that prevents this from being the case?
I think this question can be generalized to wrapping native types. E.g. you could make a SafeInt type which wraps and acts like an int, but throws on any integer overflow conditions.
There are two issues here:
Compilers may not optimize your code as well as with a native type.
For example, if you're wrapping an int, you'll likely implement overloaded arithmetic operators. A sufficiently-smart compiler will inline those methods, and the resulting code will be no different than that as with an int. In your example, a dumb compiler might be compiling a dereference in some clumsy way (e.g. get the address of the struct's start, add the offset of the pointer field (which is 0), then dereference that).
Additionally, when calling a function, the compiler may decide to pass the struct in some other way (due to e.g. poor optimization, or an ABI restriction). This could happen e.g. if the compiler doesn't pay attention to the struct's size, and treats all structs in the same way.
struct types in D may indeed have a hidden member, if you declare it in a function.
For example, the following code works:
import std.stdio;
void main()
{
string str = "I am on the stack of main()";
struct S
{
string toString() const { return str; }
}
S s;
writeln(s);
}
It works because S saves a hidden pointer to main()'s stack frame. You can force a struct to not have any hidden pointers by prefixing static to the declaration (e.g. static struct S).
There is no hidden data being passed. A struct consists exactly of what's declared in it (and any padding bytes if necessary), nothing else. There is no need to pass type information and member function information along because it's all static. Since a struct cannot inherit from another struct, there is no polymorphism.

Thread-safe data structure design

I have to design a data structure that is to be used in a multi-threaded environment. The basic API is simple: insert element, remove element, retrieve element, check that element exists. The structure's implementation uses implicit locking to guarantee the atomicity of a single API call. After i implemented this it became apparent, that what i really need is atomicity across several API calls. For example if a caller needs to check the existence of an element before trying to insert it he can't do that atomically even if each single API call is atomic:
if(!data_structure.exists(element)) {
data_structure.insert(element);
}
The example is somewhat awkward, but the basic point is that we can't trust the result of "exists" call anymore after we return from atomic context (the generated assembly clearly shows a minor chance of context switch between the two calls).
What i currently have in mind to solve this is exposing the lock through the data structure's public API. This way clients will have to explicitly lock things, but at least they won't have to create their own locks. Is there a better commonly-known solution to these kinds of problems? And as long as we're at it, can you advise some good literature on thread-safe design?
EDIT: I have a better example. Suppose that element retrieval returns either a reference or a pointer to the stored element and not it's copy. How can a caller be protected to safely use this pointer\reference after the call returns? If you think that not returning copies is a problem, then think about deep copies, i.e. objects that should also copy another objects they point to internally.
Thank you.
You either provide a mechanism for external locking (bad), or redesign the API, like putIfAbsent. The latter approach is for instance used for Java's concurrent data-structures.
And, when it comes to such basic collection types, you should check-out whether your language of choice already offers them in its standard library.
[edit]To clarify: external locking is bad for the user of the class, as it introduces another source of potential bugs. Yes, there are times, when performance considerations indeed make matters worse for concurrent data-structures than externally synchronized one, but those cases are rare, and then they usually can only be solved/optimized by people with far more knowledge/experience than me.
One, maybe important, performance hint is found in Will's answer below.
[/edit]
[edit2]Given your new example: Basically you should try to keep the synchronization of the collection and the of the elements separated as much as possible. If the lifetime of the elements is bound to its presence in one collection, you will run into problems; when using a GC this kind of problem actually becomes simpler. Otherwise you will have to use a kind of proxy instead of raw elements to be in the collection; in the simplest case for C++ you would go and use boost::shared_ptr, which uses an atomic ref-count. Insert usual performance disclaimer here. When you are using C++ (as I suspect as you talk about pointers and references), the combination of boost::shared_ptr and boost::make_shared should suffice for a while.
[/edit2]
Sometimes its expensive to create an element to be inserted. In these scenarios you can't really afford to routinely create objects that might already exist just in case they do.
One approach is for the insertIfAbsent() method to return a 'cursor' that is locked - it inserts a place-holder into the internal structure so that no other thread can believe it is absent, but does not insert the new object. The placeholder can contain a lock so that other threads that want to access that particular element must wait for it to be inserted.
In an RAII language like C++ you can use a smart stack class to encapsulate the returned cursor so that it automatically rolls-back if the calling code does not commit. In Java its a bit more deferred with the finalize() method, but can still work.
Another approach is for the caller to create the object that isn't present, but that to occasionally fail in the actual insertion if another thread has 'won the race'. This is how, for example, memcache updates are done. It can work very well.
What about moving the existance check into the .insert() method? A client calls it and if it returns false you know that something went wrong. Much like what malloc() does in plain old C -- return NULL if failed, set ERRNO.
Obviously you can also return an exception, or an instance of an object, and complicate your life up from there..
But please, don't rely on the user setting their own locks.
In an RAII style fashion you could create accessor/handle objects (don't know how its called, there probably exists a pattern of this), e.g. a List:
template <typename T>
class List {
friend class ListHandle<T>;
// private methods use NO locking
bool _exists( const T& e ) { ... }
void _insert( const T& e ) { ... }
void _lock();
void _unlock();
public:
// public methods use internal locking and just wrap the private methods
bool exists( const T& e ) {
raii_lock l;
return _exists( e );
}
void insert( const T& e ) {
raii_lock l;
_insert( e );
}
...
};
template <typename T>
class ListHandle {
List<T>& list;
public:
ListHandle( List<T>& l ) : list(l) {
list._lock();
}
~ListHandle() {
list._unlock();
}
bool exists( const T& e ) { return list._exists(e); }
void insert( const T& e ) { list._insert(e); }
};
List<int> list;
void foo() {
ListHandle<int> hndl( list ); // locks the list as long as it exists
if( hndl.exists(element) ) {
hndl.insert(element);
}
// list is unlocked here (ListHandle destructor)
}
You duplicate (or even triplicate) the public interface, but you give users the choice to choose between internal and safe and comfortable external locking wherever it is required.
First of all, you should really separate your concerns. You have two things to worry about:
The datastructure and its methods.
The thread synchronization.
I highly suggest you use an interface or virtual base class that represents the type of datastructure you are implementing. Create a simple implementation that does not do any locking, at all. Then, create a second implementation that wraps the first implementation and adds locking on top of it. This will allow a more performant implementation where locking isn't needed and will greatly simplify your code.
It looks like you are implementing some sort of dictionary. One thing you can do is provide methods that have semantics that are equivalent to the combined statement. For example setdefault is a reasonable function to provide that will set a value only if the corresponding key does not already exist in the dictionary.
In other words, my recommendation would be to figure out what combinations of methods are frequently used together, and simply create API methods that perform that combination of operations atomically.

Resources