Cannot borrow data in a `&` reference as mutable?

I used lazy_static to construct a cassandra session, which is expected to be a parameter that is always connect(always used it). However, it has the following problems:

error[E0596]: cannot borrow data in a `&` reference as mutable
   --> src/main.rs:745:29
    |
745 |     let data= cassandra_use(*SESSION, deviceid,  epoch, feature);
    |                             ^^^^^^^^ cannot borrow as mutable

The problematic code 1:

lazy_static! {
    static ref SESSION: &'static mut CassSession = unsafe {
        let cluster = create_cluster();
        let mut session_new = &mut *cass_session_new();

        let session_match = match connect_session(session_new, cluster) {
            Ok(_) => {session_new}
            _=> {
                cass_cluster_free(cluster);
                cass_session_free(session_new);
                panic!();
            }
        };
        session_match
    };
}

The problematic code 2:

let data= cassandra_use(*SESSION, deviceid, epoch, feature);

I also tried to construct a single parameter like this:

lazy_static! {
    static ref SESSION: &'static mut CassSession = unsafe {&mut *cass_session_new()};
}

But the problem is still the same:

error[E0596]: cannot borrow data in a `&` reference as mutable
   --> src/main.rs:718:27
    |
718 |     match connect_session(*SESSION, cluster) {
    |                           ^^^^^^^^ cannot borrow as mutable

The problematic code:

match connect_session(*SESSION, cluster) {
    Ok(_) => {}
    _ => {
        cass_cluster_free(cluster);
        cass_session_free(*SESSION);
        panic!();
    }
}

How can I fix the error?And lazy_static must use the life parameter ('static)?

complete code

Ypu can't put a useful &'static mut T inside a lazy_static, use interior mutability via Mutex or RwLock to get mutable references.

This is because lazy_static only implements Deref for it's hidden type.

Having the mut keyword in a static is never the right thing to do (curiously enough, someone else recently tried a similar-ish pattern, so I am thinking about making a PR on lazy_static! to error on some of these cases).

A &mut _ reference in Rust is not (directly) a mutable reference (to "an object") but a unique reference to it. And once something is stored in a static, it can be accessed from anywhere. It is thus potentially aliased, and cannot yield &mut _ references without a runtime check. In Rust's standard library, this runtime check is performed by Interior Mutability (that also needs to be Sync to be in a static): Mutex<_> or RwLock<_>.

This way, you can get (something that DerefMuts to) a &mut T from static ref SOME_GLOBAL: Mutex<T> by doing &mut SOME_GLOBAL.lock().unwrap().

You can read more about this "classic" pattern in this other post of mine.

However, your case is a little more complicated, since you are dealing with raw pointers from FFI (the first one you get is an owning pointer, such as the one you use when calling cassandra_free, the other ones are borrows of the pointee; a usual pattern in C. It is hard to spot the difference, since both are *mut CassSession).

Thus, before anything else (e.g., storing the *mut CassSession or something wrapping it in a static), you need to have safe Rust wrappers around these raw pointers, expressing the right ownership / borrowing semantics.

Example

/// The *-sys Rust crate exposing FFI functions
mod lib_sys {
    use ::libc::c_int;

    #[repr(C)]
    pub
    struct Foo {
        _opaque: [u8; 0],
    }

    extern "C" {
        pub fn new_foo () -> *mut Foo;
        pub fn use_foo (_: *mut Foo, args: c_int);
        pub fn free_foo (_: *mut Foo);
    }    
}

/*  == Time for the safe wrappers ==
 * 1. Distinguish the different *mut Foo usages (ownership or borrowing?)
 *      - new_foo() creates an owning pointer
 *      - use_foo() just borrows the pointee
 *      - free_foo() is the destructor
 *      
 * 2. Create a wrapper over *mut Foo, and express the detected semantics with that wrapper:
 *      - new_foo() should be used to create an (owned) instance of the wrapper,
 *      - use_foo() should just borrow (&mut _) from the wrapper,
 *      - free_foo() should be called on Drop::drop (and only then): 
 */

use ::std::{*,
    convert::TryInto,
};
use lib_sys as ffi;

pub
struct Foo {
    // instead of *mut ffi::Foo, let's use NonNull for more safety
    ffi: ptr::NonNull<ffi::Foo>,
}

impl Foo {
    pub
    fn new () -> Self
    {
        unsafe {
            Self {
                ffi: ptr::NonNull::new(ffi::new_foo()).unwrap(),
            }
        }
    }
    
    pub
    fn do_use (&mut self, args: i32)
    {
        unsafe {
            ffi::use_foo(
                self.ffi.as_ptr(),
                args.try_into().unwrap(), // checked conversion from i32 to c_int
            )
        }
    }
}

impl Drop for Foo {
    fn drop (&mut self)
    {
        unsafe {
            ffi::free_foo(self.ffi.as_ptr())
        }
    }
}
Usage
fn main ()
{
    let mut foo = Foo::new(); // new_foo()
    foo.do_use(42);           // use_foo()
    // mem::drop(foo);        // free_foo()
}

Then, with such a safe wrapper of *mut CassSession, you could have

lazy_static! {
    static ref SESSION: Mutex<CassSession> = Mutex::new(
        CassSession::new() // calls cass_session_new() and checks its success
    );
}
7 Likes

So, I have to repackage a layer of CassSession myself?
Because cassandra lib has been packaged(cassandra-sys-rs):

extern "C" {
    pub fn cass_session_new() -> *mut CassSession;
}

And I am not familiar with the use of libc, I just changed according to the example, if follow the this sample, I must also construct new, use and free fn?

The argument to the do_use of the sample is i32, but if I use the CassSession, how do I know the type of the argument?

In addition, can this example be executed on a physical machine? Because I tried to use it, it seems that I can't, is there something missing? (There is an extra use of std::os::raw::c_int;)

Yes, the problem is that creating a safe wrapper around a *-sys crate is complex to do properly.

I just searched a little bit more this time, and stumbled upon ::cassandra-cpp; I don't know why I missed it last time I checked, my bad.

Good news is, what you want to do is now far easier; something along these lines:

use ::cassandra-cpp::{ // 0.14.0
    Cluster,
    Session,
};

::lazy_static::lazy_static! {
    static ref SESSION: Session = {
        Cluster::default()
            .set_contact_points("127.0.0.1")
                .expect("Failed to set_contact_points")
            .set_XXX(...)
                .expect("Failed to set_XXX")
            .connect()
                .expect("Failed to connect to the cluster")
    };
}
1 Like

Because I am using the same github cassandra-cpp-sys lib. If so, do I need to call cassandra-sys lib? Or like cassandra-sys, re-construct a new struct to use Cluster and Session?

(In a way, they have some differences)

https://crates.io/crates/cassandra-cpp

I have used cassandra-cpp, I found that some of its features are not yet complete, so I chose to use cassandra-cpp-sys development, and it has been developed for a while, it is difficult to convert midway...

Pull back to the topic, I can use cassandra-cpp to wrap the Cluster and Session as a struct?

You can:

  • submit an issue to cassandra-cpp for their missing functionality w.r.t. the -sys version of their library;

  • or even better, fork cassandra-cpp, add the features yourself, and then submit a pull request for them to integrate your additions.

In any case, the -sys crates are never intended to be used directly, but to be wrapped in idiomatic and safe(r) wrappers. If you don't feel like forking their code, you can at least (provided you respect their license), reuse some of their wrappers.

For instance, instead of my abstract / generic foo wrapper example, here is what they currently do to wrap *mut Session (just some snippets of code here, slightly reformated for lisibility):

use crate::{
    future::CassFuture,
    statement::Statement,
    util::Protected,
};
use ::cassandra_sys::{
    cass_session_execute,
    cass_session_free;
    cass_session_new;
    CassSession as _Session,
};

pub
struct Session /* = */ (
   pub *mut _Session,
);

// The underlying C type has no thread-local state, and explicitly supports access
// from multiple threads: https://datastax.github.io/cpp-driver/topics/#thread-safety
unsafe impl Send for Session {}
unsafe impl Sync for Session {}

impl Protected<*mut _Session> for Session {
    fn inner(&self) -> *mut _Session { self.0 }

    /* My edit: this was erroneously not declared unsafe fn */
    unsafe fn build (inner: *mut _Session) -> Self
    {
        if inner.is_null() {
            panic!("Unexpected null pointer");
        }
        Session(inner)
    }
}

impl Drop for Session {
    /// Frees a session instance. If the session is still connected it will be synchronously
    /// closed before being deallocated.
    fn drop (self: &'_ mut Self)
    { unsafe {
        cass_session_free(self.0)
    }}
}

impl Session {
    /// Create a new Cassanda session.
    /// It's recommended to use Cluster.connect() instead
    pub fn new () -> Session
    { unsafe {
        Session(cass_session_new())
    }}



    /// Execute a statement.
    pub fn execute (&'_ self, statement: &'_ Statement) -> CassFuture<CassResult>
    { unsafe {
        CassFuture::build(cass_session_execute(self.0, statement.inner()))
    }}
    // etc.
}
1 Like

OK, I will try to pack myself. If the ability is not enough, I have to convey my boss to try to develop with cassandra-cpp, haha.

Thank for your careful answer, and it took a lot of time.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.