rcsubstring/lib.rs
1/*!
2A reference-counted substring
3
4For returning part of a string held in an [Rc] that needs to live longer than the source of the string itself.
5For more complete alternatives see [arcstr](https://crates.io/crates/arcstr) or [slice-rc](https://crates.io/crates/slice-rc).
6This is intended as a simple lightweight alternative where you just want a reference counted substring in single-threaded situations.
7
8It implements both `Deref` and `AsRef` so can be used just as a `str` in most contexts.
9
10# Example
11```rust
12# use rcsubstring::RcSubstring;
13# use std::rc::Rc;
14let shared_text: Rc<String> = Rc::new(String::from("Some text"));
15let shared_substring = RcSubstring::new(Rc::clone(&shared_text), 5..9);
16drop(shared_text);
17assert_eq!(shared_substring, "text");
18```
19
20# Use Case
21For an intended use case, consider a function that generates text and then returns an iterator over that text.
22How do we get the lifetimes to work? Even if we pass the ownership of the generated text to the iterator the
23iterator will not be allowed to pass back refs to the text it holds as it is a requirement that the values
24returned by `next()` can outlive the iterator. This is simple crate that offeres a simple solution.
25
26```rust
27# use rcsubstring::RcSubstring;
28# use std::rc::Rc;
29struct WordIterator {
30 rcstring: Rc<String>,
31 start_pos: usize,
32}
33impl Iterator for WordIterator {
34 type Item = RcSubstring;
35 fn next(&mut self) -> Option<Self::Item> {
36 let pos = self.start_pos + self.rcstring[self.start_pos..].find(" ")?;
37 let value = RcSubstring::new(Rc::clone(&self.rcstring), self.start_pos..pos);
38 self.start_pos = pos + 1;
39 return Some(value);
40 }
41}
42
43fn generate_text(values: Vec<usize>) -> String {
44 let words = vec!["zero", "one", "two", "three", "four", "five"];
45 let mut result = String::new();
46 for i in values {
47 result.push_str(words[i]);
48 result.push_str(" ");
49 }
50 result
51}
52
53fn give_me_an_iterator() -> WordIterator {
54 let text = generate_text(vec![2, 3, 1, 0, 5]);
55 WordIterator {
56 rcstring: Rc::new(text),
57 start_pos: 0,
58 }
59}
60
61let mut it = give_me_an_iterator();
62assert_eq!(it.next().unwrap(), "two");
63assert_eq!(it.next().unwrap(), "three");
64assert_eq!(it.next().unwrap(), "one");
65assert_eq!(it.next().unwrap(), "zero");
66let value = it.next().unwrap();
67drop(it);
68assert_eq!(value, "five");
69```
70
71*/
72#![warn(missing_docs)]
73use std::convert::AsRef;
74use std::fmt::{Debug, Display};
75use std::ops::{Deref, Range};
76use std::rc::Rc;
77
78/**
79A reference counted substring
80
81Stores an `Rc<String>` and a range
82The deref behaviour means this can be used just like a &str
83The advantage is the internal [Rc] handles the memory management so you don't have to worry about borrow lifetimes
84Useful for returning parts of a string that should live longer than the struct that returned them
85eg. from an iterator over a string stored in the iterator itself
86*/
87
88#[derive(Debug)]
89pub struct RcSubstring {
90 rcstring: Rc<String>,
91 range: Range<usize>,
92}
93
94impl Display for RcSubstring {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 write!(f, "{}", self.deref())
97 }
98}
99
100impl PartialEq<&str> for RcSubstring {
101 fn eq(&self, other: &&str) -> bool {
102 self.deref() == *other
103 }
104}
105
106impl RcSubstring {
107 /// Construct a new RcSubstring
108 ///
109 /// Takes the `Rc<String>` to wrap and the range for the substring in this text
110 ///
111 /// # Panics (in debug)
112 ///
113 /// Panics if `range` is invalid
114 /// - begin < end
115 /// - either begin or end > length of `Rc<String>` wrapped
116 ///
117 /// If it didn't panic here it would panic during the slice when the RcSubstring is used
118 /// so it is better to catch the issues at source.
119 ///
120 /// These panics come from debug_assert! macros that are removed in release build
121 /// for efficiency. You will still get a panic when you try to get the slice.
122 pub fn new(rcstring: Rc<String>, range: Range<usize>) -> Self {
123 debug_assert!(
124 range.end >= range.start,
125 "begin < end ({} < {}) when creating RcSubstring",
126 range.start,
127 range.end
128 );
129 debug_assert!(
130 range.start <= rcstring.len(),
131 "start index {} out of bounds when creating RcSubstring",
132 range.start
133 );
134 debug_assert!(
135 range.end <= rcstring.len(),
136 "end index {} out of bounds when creating RcSubstring",
137 range.end
138 );
139 RcSubstring { rcstring, range }
140 }
141}
142
143impl Deref for RcSubstring {
144 type Target = str;
145
146 fn deref(&self) -> &Self::Target {
147 &self.rcstring[self.range.start..self.range.end]
148 }
149}
150
151impl<T> AsRef<T> for RcSubstring
152where
153 T: ?Sized,
154 <RcSubstring as Deref>::Target: AsRef<T>,
155{
156 fn as_ref(&self) -> &T {
157 self.deref().as_ref()
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn test_basic_usage() {
167 let text = "Line 1\nLine 2\nLine 3";
168 let rcstring = Rc::new(text.to_string());
169 let pos = text.find("\n").unwrap();
170 let rcsubstring = RcSubstring::new(rcstring.clone(), 0..pos);
171 let string_rep = format!("{}", rcsubstring);
172 assert_eq!(string_rep, "Line 1");
173 let debug_rep = format!("{:?}", rcsubstring);
174 assert_eq!(
175 debug_rep,
176 "RcSubstring { rcstring: \"Line 1\\nLine 2\\nLine 3\", range: 0..6 }"
177 );
178 let pretty_rep = format!("{:#?}", rcsubstring);
179 assert_eq!(
180 pretty_rep,
181 "RcSubstring {\n rcstring: \"Line 1\\nLine 2\\nLine 3\",\n range: 0..6,\n}"
182 );
183 assert_eq!(&rcsubstring[1..2], "i");
184 }
185
186 #[test]
187 fn test_empty() {
188 let rcsubstring = RcSubstring::new(Rc::new(String::from("Random text")), 3..3);
189 assert_eq!(rcsubstring.len(), 0);
190 assert_eq!(rcsubstring, "");
191 }
192
193 #[test]
194 fn test_as_ref() {
195 fn is_hello<T: AsRef<str>>(s: T) {
196 assert_eq!(s.as_ref(), "hello");
197 }
198
199 let text = String::from("hello world!");
200 let rcss = RcSubstring::new(Rc::new(text), 0..5);
201 is_hello(rcss);
202 }
203
204 // Test these bad uses panic with our own message - ie. not in some other downstream code
205
206 #[test]
207 #[should_panic(expected = "RcSubstring")]
208 fn test_end_before_start() {
209 let _ = RcSubstring::new(Rc::new(String::from("Random text")), 3..0);
210 }
211
212 #[test]
213 #[should_panic(expected = "RcSubstring")]
214 fn test_start_out_of_range() {
215 let _ = RcSubstring::new(Rc::new(String::from("Random text")), 100..101);
216 }
217
218 #[test]
219 #[should_panic(expected = "RcSubstring")]
220 fn test_end_out_of_range() {
221 let _ = RcSubstring::new(Rc::new(String::from("Random text")), 0..101);
222 }
223}