Reputation: 671
Let's say I would like to generate a string using write!
(it might be called conditionally, inside the loop etc, it's why I prefer write!
over format!
) and then use it in multiple places (so I will need some sort of Rc
, otherwise I can't easily share it between structures). I don't need to change the string after generation, so I think that Rc<str>
is a good choice (I can avoid indirection that will cause Rc<String>
that will store a pointer to pointer).
The problem is a conversion between String
(valid argument for write!
) and Rc<str>
:
use std::fmt::Write;
pub fn generate() -> Result<Rc<str>, Box<dyn Error>> {
let mut out = String::new();
writeln!(out, "Hello {} {}", 1, 2)?; // Just an example
Ok(Rc::from(out.into_boxed_str()))
}
As I understand Rc::from
performs copying of all character data into a new memory block (because Box
and Rc
have different layouts).
Is it possible somehow utilise the knowledge that we will create Rc
later, so we can avoid copying? Maybe there are possibility to create someting like String
(at least something that accepted by write!
), but with a layout compatible with Rc
?
Upvotes: 1
Views: 777
Reputation: 27248
Your best bet is to just use the From<String>
implementation of Rc<str>
,
the String
handles growing and copying while you're still appending and then when you know the amount of memory you need you copy it once over into the Rc<str>
getting rid of any over allocation.
use std::error::Error;
use std::fmt::Write;
use std::rc::Rc;
pub fn generate() -> Result<Rc<str>, Box<dyn Error>> {
let mut out = String::new();
writeln!(out, "Hello {} {}", 1, 2)?; // Just an example
Ok(Rc::from(out))
}
You can directly allocate an Rc
with enough storage to store the whole str
and copy the data directly into it. To avoid initializing you can use MaybeUninit
.
This currently requires nightly since there is no way to create an Rc
with uninitialized storage on stable (that I know of).
#![feature(new_uninit, read_buf)]
use std::io::BorrowedBuf;
use std::rc::Rc;
use std::io::Write;
fn main() {
let a = "hello";
let b = "world";
let len = a.len() + b.len() + 2;
let mut rc = Rc::<[u8]>::new_uninit_slice(len);
let mut r = BorrowedBuf::from(Rc::get_mut(&mut rc).unwrap());
write!(r.unfilled(), "{a} {b}!").unwrap();
assert_eq!(len, r.len());
// SAFETY:
// * we wrote all bytes in the `Rc`
// * it's guaranteed valid utf-8 because we only used `write!` to do so
// * layout is the same between `Rc<[u8]>` and `Rc<str>` https://rust-lang.github.io/unsafe-code-guidelines/layout/structs-and-tuples.html#single-field-structs
let rc: Rc<str> = unsafe { std::mem::transmute(rc) };
dbg!(rc);
}
Upvotes: 1
Reputation: 70910
If you have a known length, you can do it even on stable, at the cost of zeroing the array (and unsafe
code). This is not guaranteed, but currently works without allocating because std specializes Rc
's FromIterator
for TrustedLen
iterators, and Take<Repeat>
is TrustedLen
(adapted the code from @cafce25's answer):
use std::error::Error;
use std::fmt::Write;
use std::rc::Rc;
pub fn generate() -> Result<Rc<str>, Box<dyn Error>> {
let mut rc = StrRcBuidler::new(6);
writeln!(rc, "Hello")?;
Ok(rc.build().unwrap())
}
#[derive(Debug)]
struct StrRcBuidler {
rc: Rc<[u8]>,
pos: usize,
}
impl StrRcBuidler {
fn new(len: usize) -> Self {
Self {
rc: std::iter::repeat(0).take(len).collect(),
pos: 0,
}
}
fn build(self) -> Result<Rc<str>, Self> {
if self.pos < self.rc.len() {
return Err(self);
}
// SAFETY:
// - `[u8]` has the same layout as `str`.
// - Everything is UTF-8 because we only allow writes with `fmt::Write`.
// - We checked above that all bytes are initialized.
Ok(unsafe { Rc::from_raw(Rc::into_raw(self.rc) as *const str) })
}
}
impl Write for StrRcBuidler {
fn write_str(&mut self, i: &str) -> Result<(), std::fmt::Error> {
let i = i.as_bytes();
let data = Rc::get_mut(&mut self.rc).unwrap()[self.pos..]
.get_mut(..i.len())
.ok_or(std::fmt::Error)?;
data.copy_from_slice(i);
self.pos += i.len();
Ok(())
}
}
Technically, you can use this also with only an upper limit on the length (just omit the if self.pos < self.rc.len()
check), but this will pad the string with zeroes.
Upvotes: 0