[rust-dev] wrapping a C library (ownership/mutability questions)

Josh Haberman jhaberman at gmail.com
Fri Jan 17 16:15:29 PST 2014


Hi there, thanks for the friendly replies. This is really helpful.

> You may want to think about things a little differently
> though. Using something like Arc means that the refcounting
> is managed in rust, whereas it sounds like you're already
> dealing with it in your library.

Yes, I didn't mean to imply that I would use Arc directly,
just that I would use it as a model for my own refcounted
interface (ie. by having a clone() function).

> What you'll likely end up having is two types. Both types
> internally are just an unsafe C pointer (which is
> initialized in your library) which allow interfacing with
> the underlying C object.

Yes, that sounds like the right direction to me, except that
I would *really* prefer to have one single type with mut and
non-mut versions.  If I have to introduce two types, it means
that any algorithms that operate on these types have to
either be written twice or use generics.  And they aren't
two types really, they're just mutable and non-mutable versions
of the same type, so why not model them that way?

> I think Erick answered this well. Rust allows you to specify
> whether a function requests a mutable pointer "&mut self",
> or an immutable pointer "&self", but there only way to
> forbid methods is to just have a type that doesn't have
> those methods.

This is the part that I don't follow; why can't I just mark
my mutable-only methods as taking a mutable self?  The
following example does what I expect and seems to give me what
I need:

  pub struct Box {
    priv x: i32,
  }

  impl Box {
    fn set_x(&mut self, val: i32) { self.x = val }
    fn x(&self) -> i32 { self.x }
  }

  fn main() {
    let mut var1 = Box {x: 1};
    let val1 = var1.x();
    var1.set_x(1);

    let var2 = Box {x: 2};
    let val2 = var2.x();
    // This fails to compile, because var2 is not mutable.
    var2.set_x(2);
  }

Using this model, I could have just one type and mark the
methods that are mutable-only as taking a &mut self.

> What you'll probably want is something like:
>
> impl Clone for MyImmutableStruct {
>     fn clone(&self) -> MyImmutableStruct {
>         unsafe { my_c_ref_function(self.ptr); }
>         MyImmutableStruct { ptr: self.ptr }
>     }
> }
>
> impl Drop for MyImmutableStruct {
>     fn drop(&mut self) {
>         unsafe { my_c_unref_funtion(self.ptr); }
>     }
> }

This looks about right.  What I was asking is how to furnish
the second parameter to my ref/unref functions: the "owner".
In my API all refs have an "owner", which is simply a
"const void*" that must be distinct from all other ref owners
for this object.  It's a debugging facility that makes it
easier to track down ref leaks.  In C or C++, I usually use
the address of the variable that is storing the pointer to
my MessageDef object as the ref owner.

So I want to do something sort of like:

impl Clone for MessageDef {
    fn clone(&self) -> MessageDef {
        let ret = MyImmutableStruct { ptr: self.ptr };
        unsafe { upb_msgdef_ref(ret.ptr, &ret.ptr); }
        ret
    }
}

impl Drop for MessageDef {
    fn drop(&self) {
        unsafe { upb_msgdef_unref(self.ptr, &self.ptr); }
    }
}

However I am wondering if there is any way to actually take
the address of a Rust variable as I did above (and if it is
possible, to guarantee that the address is stable over the
lifetime of the object).

Thanks,
Josh

On Fri, Jan 17, 2014 at 12:09 PM, Alex Crichton <alex at crichton.co> wrote:
>> Hi Rust experts,
>
> How flattering!
>
>> This seems like a great match for Rust, because an object could be
>> created as "mut" and local to a single task, but then "become" non-mut
>> and be sharable between tasks once frozen. And the refcounting scheme
>> sounds like a great match for the Arc model.
>
> I agree! You may want to think about things a little differently
> though. Using something like Arc means that the refcounting is managed
> in rust, whereas it sounds like you're already dealing with it in your
> library. You just want to write *wrapper* types which perform the
> relevant calls to C.
>
> What you'll likely end up having is two types. Both types internally
> are just an unsafe C pointer (which is initialized in your library)
> which allow interfacing with the underlying C object. You'll have a
> Mutable version for mutable methods and then an Immutable version for
> the methods which don't mutate (where creating the immutable version
> consumes the mutable version in rust code).
>
> I've answered some specific questions below, but if you have any more,
> feel free to ask! You can reach out to me as acrichto on IRC, and I
> always love to read code :)
>
>> 1. Can I write a "freeze" function in Rust where my mut pointers
>> "become" non-mut pointers in a way that the mut pointers are no longer
>> accessible?
>
> I think Erick answered this well. Rust allows you to specify whether a
> function requests a mutable pointer "&mut self", or an immutable
> pointer "&self", but there only way to forbid methods is to just have
> a type that doesn't have those methods.
>
>> 2. Is my notion of freezing the same as Rust's "Freeze" trait? Is
>> there a pattern I should follow to make my type "fit in" with the
>> stdlib better?
>
> Not quite. The rust Freeze trait is a "kind" which basically means
> that the compiler will infer it based on the structure of a type. This
> kind is the notion that an object cannot be mutated through a &self
> pointer (for example Cell is *not* Freeze).
>
> Your object does indeed ascribe to Freeze, but you don't need to worry
> about dealing with the Freeze trait itself. Your library already deals
> with freezing internally, so when you write Rust bindings the best way
> to expose this would be to have separate types for the
> mutable/immutable methods. As Erick suggested, creating the immutable
> would consume the mutable type. Under the hood it would look like:
>
> pub struct MyMutableBuilder {
>     priv ptr: *my_c_type_t
> }
>
> pub struct MyImmutableStruct {
>     priv ptr: *my_c_type_t
> }
>
>> 3. I think I can write a very Arc-like interface to my refcounting.
>> Will my individual refcounting wrappers let me take their address so I
>> can pass it to the second param of my ref/unref functions?
>
> I'm a little confused by this question because this sounds like you
> want to *port* your library to Rust rather than *wrap* your library
> with Rust. If you're porting, then I would certainly recommend Arc. If
> you're wrapping, then you wouldn't need Arc because you're already
> doing that atomic refcounts yourself.
>
> What you'll probably want is something like:
>
> impl Clone for MyImmutableStruct {
>     fn clone(&self) -> MyImmutableStruct {
>         unsafe { my_c_ref_function(self.ptr); }
>         MyImmutableStruct { ptr: self.ptr }
>     }
> }
>
> impl Drop for MyImmutableStruct {
>     fn drop(&mut self) {
>         unsafe { my_c_unref_funtion(self.ptr); }
>     }
> }
>
> Basically, when you clone() your object, it bumps the refcount. You
> can then send the clone'd object to another thread. When the objects
> go out of scope (get destroyed) they'll deref the refcount, allowing
> you to safely clean things up.


More information about the Rust-dev mailing list