reaper_medium/
string_types.rs

1use std::borrow::Cow;
2use std::ffi::{CStr, CString};
3use std::os::raw::c_char;
4
5/// A string parameter.
6///
7/// Medium-level API functions with string parameters accept all kinds of strings which can be
8/// converted into this type, most notably `&CStr` and `&str`.
9///
10/// # Design
11///
12/// This is a wrapper around a `Cow<CStr>`.
13///
14/// ## Why C strings and not regular Rust strings?
15///
16/// We use a sort of C string because that perfectly accounts for the medium-level API's design goal
17/// to be still as close to the original REAPER API as possible (while at the same time introducing
18/// Rust's type safety). The C++ REAPER API generally expects C strings (`*const c_char`).
19/// Fortunately UTF-8 encoded ones - which makes a character set conversion unnecessary.
20///
21/// ## Why `&CStr` and not `*const c_char`?
22///
23/// We don't use `*const c_char` directly because we want more type safety. We use `&CStr` instead
24/// because in Rust that's the closest thing to a `*const c_char` (infact it's the same + some
25/// additional guarantees). It's a reference instead of a pointer so we can assume it's neither
26/// stale nor `null`. Also, the `&CStr` type gives us important guarantees, for example that there
27/// are no intermediate zero bytes - which would make the string end abruptly in C world.
28///
29/// ## Why `Cow` and `ReaperStringArg`?
30///
31/// We don't use just a plain `&CStr` as parameter type because `&CStr` is not the regular string
32/// type in Rust. It's much harder to create and use than `&str`. We want the API to be a pleasure
33/// to use! That's the reason for adding `ReaperStringArg` and `Cow` to the mix. `Cow`is necessary
34/// because we might need to own a possible conversion result (e.g. from `&str`). `ReaperStringArg`
35/// is necessary to offer implicit conversions from regular Rust string types. Because medium-level
36/// API functions take string parameters as `impl Into<ReaperStringArg>`, they *just work* with
37/// regular Rust strings.
38///
39/// ## Performance considerations
40///
41/// A conversion from a regular Rust string is not entirely without cost because we need to check
42/// for intermediate zero bytes and append a zero byte (which demands a copy if a borrowed string is
43/// passed)! Therefore, if you want to be sure to not waste any performance and you can get cheap
44/// access to a C string, just pass that one directly. Then there's no extra cost involved. In many
45/// scenarios this is probably over optimization, but the point is, you *can* go the zero-cost way,
46/// if you want.
47///
48/// In the *reaper-rs*  code base you will find many examples that pass `c_str!("...")` to string
49/// parameters. This macro from the [c_str_macro crate](https://crates.io/crates/c_str_macro)
50/// creates static (UTF-8 encoded) `&CStr` literals, just as `"..."` creates static `&str` literals.
51/// Because those literals are embedded in the binary itself, no heap-space allocation or conversion
52/// is necessary at all. If you want, you can do the same with your literals.
53pub struct ReaperStringArg<'a>(Cow<'a, CStr>);
54
55impl<'a> ReaperStringArg<'a> {
56    /// Returns a raw pointer to the string. Used by code in this crate only.
57    pub(super) fn as_ptr(&self) -> *const c_char {
58        self.0.as_ptr()
59    }
60
61    /// Consumes this string and spits out the contained cow. Used by code in this crate only.
62    pub(super) fn into_inner(self) -> Cow<'a, CStr> {
63        self.0
64    }
65}
66
67// This is the most important conversion because it's the ideal case (zero-cost). For now we don't
68// offer a conversion from `CString` (owned) because it could confuse consumers. They might start to
69// think that string arguments are always consumed, which is not the case. If there's much demand,
70// we can still add that later.
71impl<'a> From<&'a CStr> for ReaperStringArg<'a> {
72    fn from(s: &'a CStr) -> Self {
73        ReaperStringArg(s.into())
74    }
75}
76
77// This is the second most important conversion because we want consumers to be able to just pass a
78// normal string literal.
79impl<'a> From<&'a str> for ReaperStringArg<'a> {
80    fn from(s: &'a str) -> Self {
81        // Requires copying
82        ReaperStringArg(
83            CString::new(s)
84                .expect("Rust string too exotic for REAPER")
85                .into(),
86        )
87    }
88}
89
90// This conversion might appear somewhat unusual because it takes something *owned*. But that has a
91// good reason: If there's a `String` which the consumer is okay to give away (move), this is
92// good for performance because no copy needs to be made in order to convert this into a C string.
93// By introducing this conversion, we want to encourage this scenario.
94impl<'a> From<String> for ReaperStringArg<'a> {
95    fn from(s: String) -> Self {
96        // Doesn't require copying because we own the string now
97        ReaperStringArg(
98            CString::new(s)
99                .expect("Rust string too exotic for REAPER")
100                .into(),
101        )
102    }
103}