thomasmost
thomasmost

Reputation: 1070

Is there a way to wrap an &str to a &T(String) without cloning?

In Rust, I frequently wrap Strings with custom structs for the purpose of disambiguating trait behavior. For example, I might have a data access trait called IGetByField<T,F> implemented like so:


pub struct ParentObjectId(pub String);

impl<'r> IGetByField<Object, ParentObjectId> for ObjectDao<'r> {
  fn get_by_field(&self, parent_object_id: &ParentObjectId) -> Vec<Org> {
    let connection = &mut unpool!(self.pool.get());
    DB::get_object_by_parent_id(connection, &parent_object_id.0)
  }
}

My question is, when invoking this method on the ObjectDao and wrapping a string ID, is there a way to go from a &str type to a &ParentObjectId without cloning the id reference?

i.e. my current code:

let some_parent_id: &str = "some_id";

let object = object_dao.get_by_field(&ParentObjectId(some_parent_id.clone()));

Is there a way to do this without cloning?

Upvotes: 0

Views: 236

Answers (1)

ChrisB
ChrisB

Reputation: 3714

If there's a will, there's a way:

fn foo(id: &str) {
    let frankenstring = std::mem::ManuallyDrop::new(unsafe { 
        String::from_raw_parts(id.as_ptr() as *mut u8, id.len(), id.len()) 
    });
    call_whatever_function_wants_the_string(&frankenstring);
}

This is of course pretty horrible, but it illustrates a point.

  • A String is an owned buffer of bytes with a capacity and a length. It will deallocate this buffer when it is dropped. It's effectively a Vec<u8> that enforces itself to be utf-8 encoded.

  • A &str is a borrowed reference to a sequence of utf-8 encoded bytes.

These two are not the same, and cannot be used interchangibly. A String can give you a &str, but not the other way around. Not every &str comes from a String, so a &str has no way of giving you a reference to a triple of (pointer, length, capacity) anywhere in memory that would represent itself.

Note: please don't use the above code snippet in production. While it will most likely work, it violates the safety invariants of String::from_raw_parts and therefore might become undefined behavior in future versions of Rust.


The problem that you have is very common though. There are a few ways of solving this:

  1. Cow. This type's whole purpose is to encapsulate this kind of dichotomy:
use std::borrow::Cow;

pub struct ParentObjectId<'a>(pub Cow<'a, str>);

pub fn foo(id: &str) {
    let po = ParentObjectId(id.into());
    call_whatever_function_wants_the_po_id(&po);
}
pub fn bar(id: String) {
    let po = ParentObjectId(id.into());
    call_whatever_function_wants_the_po_id(&po);
}
  1. Make your id type not require ownership: struct ParentObjectId<'a>(&'a str);. If you need to store ParentObjectsIds, use a separate type: struct ParentObjectIdStored(String);

  2. Some sort of string interning system so you only have to pass around integer Ids.

  3. Use Rc / Arc everywhere to avoid deciding on ownership at every function call boundary.

  4. Just clone. Maybe that's fine.

... and probably many more. But I'll leave it at that for now.

Upvotes: 1

Related Questions