Skip to content

Commit d262612

Browse files
committed
Implement String-like wrapper around FuriString
1 parent 6c2ca6b commit d262612

File tree

2 files changed

+262
-0
lines changed

2 files changed

+262
-0
lines changed

crates/flipperzero/src/furi/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
pub mod io;
44
pub mod message_queue;
5+
pub mod string;
56
pub mod sync;
67
pub mod thread;
78

crates/flipperzero/src/furi/string.rs

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
//! String primitives built around `FuriString`.
2+
3+
use core::{
4+
cmp::Ordering,
5+
convert::Infallible,
6+
ffi::{c_char, CStr},
7+
fmt,
8+
};
9+
10+
#[cfg(feature = "alloc")]
11+
use alloc::ffi::CString;
12+
13+
use flipperzero_sys as sys;
14+
15+
/// A Furi string.
16+
///
17+
/// This is similar to Rust's [`CString`] in that it represents an owned, C-compatible,
18+
/// nul-terminated string with no nul bytes in the middle. It also has additional methods
19+
/// to provide the flexibility of Rust's [`String`]. It is used in various APIs of the
20+
/// Flipper Zero SDK.
21+
///
22+
/// This type does not requre the `alloc` feature flag, because it does not use the Rust
23+
/// allocator. Very short strings (7 bytes or fewer) are stored directly inside the
24+
/// `FuriString` struct (which is stored on the heap), while longer strings are allocated
25+
/// on the heap by the Flipper Zero firmware.
26+
#[derive(Eq)]
27+
pub struct String(*mut sys::FuriString);
28+
29+
impl Drop for String {
30+
fn drop(&mut self) {
31+
unsafe { sys::furi_string_free(self.0) };
32+
}
33+
}
34+
35+
// Implementations matching `std::string::String`.
36+
impl String {
37+
/// Creates a new empty `String`.
38+
#[inline]
39+
#[must_use]
40+
pub fn new() -> Self {
41+
String(unsafe { sys::furi_string_alloc() })
42+
}
43+
44+
/// Creates a new empty `String` with at least the specified capacity.
45+
#[inline]
46+
#[must_use]
47+
pub fn with_capacity(capacity: usize) -> Self {
48+
let s = Self::new();
49+
// The size passed to `sys::furi_string_reserve` needs to include the nul
50+
// terminator.
51+
unsafe { sys::furi_string_reserve(s.0, capacity + 1) };
52+
s
53+
}
54+
55+
/// Extracts a `CStr` containing the entire string slice, with nul termination.
56+
#[inline]
57+
#[must_use]
58+
pub fn as_c_str(&self) -> &CStr {
59+
unsafe { CStr::from_ptr(sys::furi_string_get_cstr(self.0)) }
60+
}
61+
62+
/// Appends a given `String` onto the end of this `String`.
63+
#[inline]
64+
pub fn push_string(&mut self, string: &String) {
65+
unsafe { sys::furi_string_cat(self.0, string.0) }
66+
}
67+
68+
/// Appends a given Rust `str` onto the end of this `String`.
69+
#[inline]
70+
pub fn push_rust_str(&mut self, string: &str) {
71+
self.reserve(string.len());
72+
for ch in string.chars() {
73+
self.push(ch);
74+
}
75+
}
76+
77+
/// Appends a given `CStr` onto the end of this `String`.
78+
#[inline]
79+
pub fn push_c_str(&mut self, string: &CStr) {
80+
unsafe { sys::furi_string_cat_str(self.0, string.as_ptr()) }
81+
}
82+
83+
/// Reserves capacity for at least `additional` bytes more than the current length.
84+
#[inline]
85+
pub fn reserve(&mut self, additional: usize) {
86+
// `self.len()` counts the number of bytes excluding the terminating nul, but the
87+
// size passed to `sys::furi_string_reserve` needs to include the nul terminator.
88+
unsafe { sys::furi_string_reserve(self.0, self.len() + additional + 1) };
89+
}
90+
91+
/// Appends the given [`char`] to the end of this `String`.
92+
#[inline]
93+
pub fn push(&mut self, ch: char) {
94+
match ch.len_utf8() {
95+
1 => unsafe { sys::furi_string_push_back(self.0, ch as c_char) },
96+
_ => unsafe { sys::furi_string_utf8_push(self.0, ch as u32) },
97+
}
98+
}
99+
100+
/// Returns a byte slice of this `String`'s contents.
101+
///
102+
/// The returned slice will **not** contain the trailing nul terminator that the
103+
/// underlying C string has.
104+
#[inline]
105+
#[must_use]
106+
pub fn to_bytes(&self) -> &[u8] {
107+
self.as_c_str().to_bytes()
108+
}
109+
110+
/// Returns a byte slice of this `String`'s contents with the trailing nul byte.
111+
///
112+
/// This function is the equivalent of [`String::to_bytes`] except that it will retain
113+
/// the trailing nul terminator instead of chopping it off.
114+
#[inline]
115+
#[must_use]
116+
pub fn to_bytes_with_nul(&self) -> &[u8] {
117+
self.as_c_str().to_bytes_with_nul()
118+
}
119+
120+
/// Shortens this `String` to the specified length.
121+
///
122+
/// If `new_len` is greater than the string's current length, this has no effect.
123+
#[inline]
124+
pub fn truncate(&mut self, new_len: usize) {
125+
unsafe { sys::furi_string_left(self.0, new_len) };
126+
}
127+
128+
/// Returns the length of this `String`.
129+
///
130+
/// This length is in bytes, not [`char`]s or graphemes. In other words, it might not
131+
/// be what a human considers the length of the string.
132+
#[inline]
133+
#[must_use]
134+
pub fn len(&self) -> usize {
135+
unsafe { sys::furi_string_size(self.0) }
136+
}
137+
138+
/// Returns `true` if this `String` has a length of zero, and `false` otherwise.
139+
#[inline]
140+
#[must_use]
141+
pub fn is_empty(&self) -> bool {
142+
unsafe { sys::furi_string_empty(self.0) }
143+
}
144+
145+
/// Splits the string into two at the given byte index.
146+
///
147+
/// Returns a newly allocated `String`. `self` contains bytes `[0, at)`, and the
148+
/// returned `String` contains bytes `[at, len)`.
149+
///
150+
/// Note that the capacity of `self` does not change.
151+
///
152+
/// # Panics
153+
///
154+
/// Panics if `at` is beyond the last byte of the string.
155+
#[inline]
156+
#[must_use = "use `.truncate()` if you don't need the other half"]
157+
pub fn split_off(&mut self, at: usize) -> String {
158+
// SAFETY: Trimming the beginning of a C string results in a valid C string, as
159+
// long as the nul byte is not trimmed.
160+
assert!(at <= self.len());
161+
let ret =
162+
String(unsafe { sys::furi_string_alloc_set_str(self.as_c_str().as_ptr().add(at)) });
163+
self.truncate(at);
164+
ret
165+
}
166+
167+
/// Truncates this `String`, removing all contents.
168+
///
169+
/// While this means the `String` will have a length of zero, it does not touch its
170+
/// capacity.
171+
#[inline]
172+
pub fn clear(&mut self) {
173+
unsafe { sys::furi_string_reset(self.0) };
174+
}
175+
}
176+
177+
impl Default for String {
178+
fn default() -> Self {
179+
Self::new()
180+
}
181+
}
182+
183+
impl AsRef<CStr> for String {
184+
fn as_ref(&self) -> &CStr {
185+
self.as_c_str()
186+
}
187+
}
188+
189+
impl PartialEq for String {
190+
fn eq(&self, other: &Self) -> bool {
191+
unsafe { sys::furi_string_equal(self.0, other.0) }
192+
}
193+
}
194+
195+
impl PartialEq<CStr> for String {
196+
fn eq(&self, other: &CStr) -> bool {
197+
unsafe { sys::furi_string_equal_str(self.0, other.as_ptr()) }
198+
}
199+
}
200+
201+
impl PartialEq<String> for CStr {
202+
fn eq(&self, other: &String) -> bool {
203+
other.eq(self)
204+
}
205+
}
206+
207+
#[cfg(feature = "alloc")]
208+
impl PartialEq<CString> for String {
209+
fn eq(&self, other: &CString) -> bool {
210+
self.eq(other.as_c_str())
211+
}
212+
}
213+
214+
#[cfg(feature = "alloc")]
215+
impl PartialEq<String> for CString {
216+
fn eq(&self, other: &String) -> bool {
217+
other.eq(self.as_c_str())
218+
}
219+
}
220+
221+
impl Ord for String {
222+
fn cmp(&self, other: &Self) -> Ordering {
223+
match unsafe { sys::furi_string_cmp(self.0, other.0) } {
224+
..=-1 => Ordering::Less,
225+
0 => Ordering::Equal,
226+
1.. => Ordering::Greater,
227+
}
228+
}
229+
}
230+
231+
impl PartialOrd for String {
232+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
233+
Some(self.cmp(other))
234+
}
235+
}
236+
237+
impl fmt::Write for String {
238+
fn write_str(&mut self, s: &str) -> fmt::Result {
239+
self.push_rust_str(s);
240+
Ok(())
241+
}
242+
243+
fn write_char(&mut self, c: char) -> fmt::Result {
244+
self.push(c);
245+
Ok(())
246+
}
247+
}
248+
249+
impl ufmt::uWrite for String {
250+
type Error = Infallible;
251+
252+
fn write_str(&mut self, s: &str) -> Result<(), Self::Error> {
253+
self.push_rust_str(s);
254+
Ok(())
255+
}
256+
257+
fn write_char(&mut self, c: char) -> Result<(), Self::Error> {
258+
self.push(c);
259+
Ok(())
260+
}
261+
}

0 commit comments

Comments
 (0)