Skip to content

Commit

Permalink
collections: Stabilize String
Browse files Browse the repository at this point in the history
# Rationale

When dealing with strings, many functions deal with either a `char` (unicode
codepoint) or a byte (utf-8 encoding related). There is often an inconsistent
way in which methods are referred to as to whether they contain "byte", "char",
or nothing in their name.  There are also issues open to rename *all* methods to
reflect that they operate on utf8 encodings or bytes (e.g. utf8_len() or
byte_len()).

The current state of String seems to largely be what is desired, so this PR
proposes the following rationale for methods dealing with bytes or characters:

> When constructing a string, the input encoding *must* be mentioned (e.g.
> from_utf8). This makes it clear what exactly the input type is expected to be
> in terms of encoding.
>
> When a method operates on anything related to an *index* within the string
> such as length, capacity, position, etc, the method *implicitly* operates on
> bytes. It is an understood fact that String is a utf-8 encoded string, and
> burdening all methods with "bytes" would be redundant.
>
> When a method operates on the *contents* of a string, such as push() or pop(),
> then "char" is the default type. A String can loosely be thought of as being a
> collection of unicode codepoints, but not all collection-related operations
> make sense because some can be woefully inefficient.

# Method stabilization

The following methods have been marked #[stable]

* The String type itself
* String::new
* String::with_capacity
* String::from_utf16_lossy
* String::into_bytes
* String::as_bytes
* String::len
* String::clear
* String::as_slice

The following methods have been marked #[unstable]

* String::from_utf8 - The error type in the returned `Result` may change to
                      provide a nicer message when it's `unwrap()`'d
* String::from_utf8_lossy - The returned `MaybeOwned` type still needs
                            stabilization
* String::from_utf16 - The return type may change to become a `Result` which
                       includes more contextual information like where the error
                       occurred.
* String::from_chars - This is equivalent to iter().collect(), but currently not
                       as ergonomic.
* String::from_char - This method is the equivalent of Vec::from_elem, and has
                      been marked #[unstable] becuase it can be seen as a
                      duplicate of iterator-based functionality as well as
                      possibly being renamed.
* String::push_str - This *can* be emulated with .extend(foo.chars()), but is
                     less efficient because of decoding/encoding. Due to the
                     desire to minimize API surface this may be able to be
                     removed in the future for something possibly generic with
                     no loss in performance.
* String::grow - This is a duplicate of iterator-based functionality, which may
                 become more ergonomic in the future.
* String::capacity - This function was just added.
* String::push - This function was just added.
* String::pop - This function was just added.
* String::truncate - The failure conventions around String methods and byte
                     indices isn't totally clear at this time, so the failure
                     semantics and return value of this method are subject to
                     change.
* String::as_mut_vec - the naming of this method may change.
* string::raw::* - these functions are all waiting on [an RFC][2]

[2]: rust-lang/rfcs#240

The following method have been marked #[experimental]

* String::from_str - This function only exists as it's more efficient than
                     to_string(), but having a less ergonomic function for
                     performance reasons isn't the greatest reason to keep it
                     around. Like Vec::push_all, this has been marked
                     experimental for now.

The following methods have been #[deprecated]

* String::append - This method has been deprecated to remain consistent with the
                   deprecation of Vec::append. While convenient, it is one of
                   the only functional-style apis on String, and requires more
                   though as to whether it belongs as a first-class method or
                   now (and how it relates to other collections).
* String::from_byte - This is fairly rare functionality and can be emulated with
                      str::from_utf8 plus an assert plus a call to to_string().
                      Additionally, String::from_char could possibly be used.
* String::byte_capacity - Renamed to String::capacity due to the rationale
                          above.
* String::push_char - Renamed to String::push due to the rationale above.
* String::pop_char - Renamed to String::pop due to the rationale above.
* String::push_bytes - There are a number of `unsafe` functions on the `String`
                       type which allow bypassing utf-8 checks. These have all
                       been deprecated in favor of calling `.as_mut_vec()` and
                       then operating directly on the vector returned. These
                       methods were deprecated because naming them with relation
                       to other methods was difficult to rationalize and it's
                       arguably more composable to call .as_mut_vec().
* String::as_mut_bytes - See push_bytes
* String::push_byte - See push_bytes
* String::pop_byte - See push_bytes
* String::shift_byte - See push_bytes

# Reservation methods

This commit does not yet touch the methods for reserving bytes. The methods on
Vec have also not yet been modified. These methods are discussed in the upcoming
[Collections reform RFC][1]

[1]: https://github.com/aturon/rfcs/blob/collections-conventions/active/0000-collections-conventions.md#implicit-growth
  • Loading branch information
alexcrichton committed Sep 22, 2014
1 parent 3907a13 commit 79b4ce0
Showing 1 changed file with 77 additions and 9 deletions.
86 changes: 77 additions & 9 deletions src/libcollections/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use vec::Vec;

/// A growable string stored as a UTF-8 encoded buffer.
#[deriving(Clone, PartialEq, PartialOrd, Eq, Ord)]
#[stable]
pub struct String {
vec: Vec<u8>,
}
Expand All @@ -44,6 +45,7 @@ impl String {
/// let mut s = String::new();
/// ```
#[inline]
#[stable]
pub fn new() -> String {
String {
vec: Vec::new(),
Expand All @@ -60,6 +62,7 @@ impl String {
/// let mut s = String::with_capacity(10);
/// ```
#[inline]
#[stable]
pub fn with_capacity(capacity: uint) -> String {
String {
vec: Vec::with_capacity(capacity),
Expand All @@ -75,6 +78,7 @@ impl String {
/// assert_eq!(s.as_slice(), "hello");
/// ```
#[inline]
#[experimental = "needs investigation to see if to_string() can match perf"]
pub fn from_str(string: &str) -> String {
String { vec: string.as_bytes().to_vec() }
}
Expand Down Expand Up @@ -111,6 +115,7 @@ impl String {
/// assert_eq!(s, Err(vec![240, 144, 128]));
/// ```
#[inline]
#[unstable = "error type may change"]
pub fn from_utf8(vec: Vec<u8>) -> Result<String, Vec<u8>> {
if str::is_utf8(vec.as_slice()) {
Ok(String { vec: vec })
Expand All @@ -129,6 +134,7 @@ impl String {
/// let output = String::from_utf8_lossy(input);
/// assert_eq!(output.as_slice(), "Hello \uFFFDWorld");
/// ```
#[unstable = "return type may change"]
pub fn from_utf8_lossy<'a>(v: &'a [u8]) -> MaybeOwned<'a> {
if str::is_utf8(v) {
return MaybeOwnedSlice(unsafe { mem::transmute(v) })
Expand Down Expand Up @@ -260,6 +266,7 @@ impl String {
/// v[4] = 0xD800;
/// assert_eq!(String::from_utf16(v), None);
/// ```
#[unstable = "error value in return may change"]
pub fn from_utf16(v: &[u16]) -> Option<String> {
let mut s = String::with_capacity(v.len() / 2);
for c in str::utf16_items(v) {
Expand All @@ -284,6 +291,7 @@ impl String {
/// assert_eq!(String::from_utf16_lossy(v),
/// "𝄞mus\uFFFDic\uFFFD".to_string());
/// ```
#[stable]
pub fn from_utf16_lossy(v: &[u16]) -> String {
str::utf16_items(v).map(|c| c.to_char_lossy()).collect()
}
Expand All @@ -298,6 +306,7 @@ impl String {
/// assert_eq!(s.as_slice(), "hello");
/// ```
#[inline]
#[unstable = "may be removed in favor of .collect()"]
pub fn from_chars(chs: &[char]) -> String {
chs.iter().map(|c| *c).collect()
}
Expand All @@ -312,6 +321,7 @@ impl String {
/// assert_eq!(bytes, vec![104, 101, 108, 108, 111]);
/// ```
#[inline]
#[stable]
pub fn into_bytes(self) -> Vec<u8> {
self.vec
}
Expand All @@ -329,6 +339,7 @@ impl String {
/// assert_eq!(big.as_slice(), "hello world!");
/// ```
#[inline]
#[deprecated = "use .push_str() instead"]
pub fn append(mut self, second: &str) -> String {
self.push_str(second);
self
Expand All @@ -343,6 +354,8 @@ impl String {
/// assert_eq!(s.as_slice(), "aaaaa");
/// ```
#[inline]
#[unstable = "may be replaced with iterators, questionable usability, and \
the name may change"]
pub fn from_char(length: uint, ch: char) -> String {
if length == 0 {
return String::new()
Expand Down Expand Up @@ -370,6 +383,7 @@ impl String {
/// let s = String::from_byte(104);
/// assert_eq!(s.as_slice(), "h");
/// ```
#[deprecated = "use str::from_utf8 with a slice of one byte instead"]
pub fn from_byte(b: u8) -> String {
assert!(b < 128u8);
String::from_char(1, b as char)
Expand All @@ -385,6 +399,7 @@ impl String {
/// assert_eq!(s.as_slice(), "foobar");
/// ```
#[inline]
#[unstable = "extra variants of `push`, could possibly be based on iterators"]
pub fn push_str(&mut self, string: &str) {
self.vec.push_all(string.as_bytes())
}
Expand All @@ -399,6 +414,7 @@ impl String {
/// assert_eq!(s.as_slice(), "fooZZZZZ");
/// ```
#[inline]
#[unstable = "duplicate of iterator-based functionality"]
pub fn grow(&mut self, count: uint, ch: char) {
for _ in range(0, count) {
self.push_char(ch)
Expand All @@ -414,10 +430,25 @@ impl String {
/// assert!(s.byte_capacity() >= 10);
/// ```
#[inline]
#[deprecated = "renamed to .capacity()"]
pub fn byte_capacity(&self) -> uint {
self.vec.capacity()
}

/// Returns the number of bytes that this string buffer can hold without reallocating.
///
/// # Example
///
/// ```
/// let s = String::with_capacity(10);
/// assert!(s.byte_capacity() >= 10);
/// ```
#[inline]
#[unstable = "just implemented, needs to prove itself"]
pub fn capacity(&self) -> uint {
self.vec.capacity()
}

/// Reserves capacity for at least `extra` additional bytes in this string buffer.
///
/// # Example
Expand Down Expand Up @@ -477,19 +508,27 @@ impl String {
self.vec.shrink_to_fit()
}

/// Deprecated, use .push() instead.
#[inline]
#[deprecated = "renamed to .push()"]
pub fn push_char(&mut self, ch: char) {
self.push(ch)
}

/// Adds the given character to the end of the string.
///
/// # Example
///
/// ```
/// let mut s = String::from_str("abc");
/// s.push_char('1');
/// s.push_char('2');
/// s.push_char('3');
/// s.push('1');
/// s.push('2');
/// s.push('3');
/// assert_eq!(s.as_slice(), "abc123");
/// ```
#[inline]
pub fn push_char(&mut self, ch: char) {
#[stable = "function just renamed from push_char"]
pub fn push(&mut self, ch: char) {
let cur_len = self.len();
// This may use up to 4 bytes.
self.vec.reserve_additional(4);
Expand Down Expand Up @@ -520,6 +559,7 @@ impl String {
/// assert_eq!(s.as_slice(), "hello");
/// ```
#[inline]
#[deprecated = "call .as_mut_vec() and push onto that"]
pub unsafe fn push_bytes(&mut self, bytes: &[u8]) {
self.vec.push_all(bytes)
}
Expand All @@ -534,6 +574,7 @@ impl String {
/// assert_eq!(s.as_bytes(), b);
/// ```
#[inline]
#[stable]
pub fn as_bytes<'a>(&'a self) -> &'a [u8] {
self.vec.as_slice()
}
Expand All @@ -557,6 +598,7 @@ impl String {
/// assert_eq!(s.as_slice(), "h3ll0")
/// ```
#[inline]
#[deprecated = "call .as_mut_vec().as_slice() instead"]

This comment has been minimized.

Copy link
@aturon

aturon Sep 22, 2014

"call .as_mut_vec().as_mut_slice() instead"

pub unsafe fn as_mut_bytes<'a>(&'a mut self) -> &'a mut [u8] {
self.vec.as_mut_slice()
}
Expand All @@ -575,6 +617,7 @@ impl String {
/// assert_eq!(s.as_slice(), "he");
/// ```
#[inline]
#[unstable = "the failure conventions for strings are under development"]
pub fn truncate(&mut self, len: uint) {
assert!(self.as_slice().is_char_boundary(len));
self.vec.truncate(len)
Expand All @@ -595,6 +638,7 @@ impl String {
/// assert_eq!(s.as_slice(), "hello");
/// ```
#[inline]
#[deprecated = "call .as_mut_vec().push() instead"]
pub unsafe fn push_byte(&mut self, byte: u8) {
self.vec.push(byte)
}
Expand All @@ -617,6 +661,7 @@ impl String {
/// }
/// ```
#[inline]
#[deprecated = "call .as_mut_vec().pop() instead"]
pub unsafe fn pop_byte(&mut self) -> Option<u8> {
let len = self.len();
if len == 0 {
Expand All @@ -628,20 +673,26 @@ impl String {
Some(byte)
}

/// Deprecated. Renamed to `pop`.
#[inline]
#[deprecated = "renamed to .pop()"]
pub fn pop_char(&mut self) -> Option<char> { self.pop() }

/// Removes the last character from the string buffer and returns it.
/// Returns `None` if this string buffer is empty.
///
/// # Example
///
/// ```
/// let mut s = String::from_str("foo");
/// assert_eq!(s.pop_char(), Some('o'));
/// assert_eq!(s.pop_char(), Some('o'));
/// assert_eq!(s.pop_char(), Some('f'));
/// assert_eq!(s.pop_char(), None);
/// assert_eq!(s.pop(), Some('o'));
/// assert_eq!(s.pop(), Some('o'));
/// assert_eq!(s.pop(), Some('f'));
/// assert_eq!(s.pop(), None);
/// ```
#[inline]
pub fn pop_char(&mut self) -> Option<char> {
#[unstable = "this function was just renamed from pop_char"]
pub fn pop(&mut self) -> Option<char> {
let len = self.len();
if len == 0 {
return None
Expand Down Expand Up @@ -671,6 +722,7 @@ impl String {
/// assert_eq!(s.shift_byte(), None);
/// }
/// ```
#[deprecated = "call .as_mut_rev().remove(0)"]

This comment has been minimized.

Copy link
@aturon

aturon Sep 22, 2014

"call .as_mut_vec().remove(0)"

pub unsafe fn shift_byte(&mut self) -> Option<u8> {
self.vec.remove(0)
}
Expand Down Expand Up @@ -722,25 +774,31 @@ impl String {
/// }
/// assert_eq!(s.as_slice(), "olleh");
/// ```
#[unstable = "the name of this method may be changed"]
pub unsafe fn as_mut_vec<'a>(&'a mut self) -> &'a mut Vec<u8> {
&mut self.vec
}
}

#[experimental = "collection traits will probably be removed"]
impl Collection for String {
#[inline]
#[stable]
fn len(&self) -> uint {
self.vec.len()
}
}

#[experimental = "collection traits will probably be removed"]
impl Mutable for String {
#[inline]
#[stable]
fn clear(&mut self) {
self.vec.clear()
}
}

#[experimental = "waiting on FromIterator stabilization"]
impl FromIterator<char> for String {
fn from_iter<I:Iterator<char>>(iterator: I) -> String {
let mut buf = String::new();
Expand All @@ -749,6 +807,7 @@ impl FromIterator<char> for String {
}
}

#[experimental = "waiting on Extendable stabilization"]
impl Extendable<char> for String {
fn extend<I:Iterator<char>>(&mut self, mut iterator: I) {
for ch in iterator {
Expand All @@ -757,48 +816,56 @@ impl Extendable<char> for String {
}
}

#[experimental = "waiting on Str stabilization"]
impl Str for String {
#[inline]
#[stable]
fn as_slice<'a>(&'a self) -> &'a str {
unsafe {
mem::transmute(self.vec.as_slice())
}
}
}

#[experimental = "waiting on StrAllocating stabilization"]
impl StrAllocating for String {
#[inline]
fn into_string(self) -> String {
self
}
}

#[stable]
impl Default for String {
fn default() -> String {
String::new()
}
}

#[experimental = "waiting on Show stabilization"]
impl fmt::Show for String {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.as_slice().fmt(f)
}
}

#[experimental = "waiting on Hash stabilization"]
impl<H: hash::Writer> hash::Hash<H> for String {
#[inline]
fn hash(&self, hasher: &mut H) {
self.as_slice().hash(hasher)
}
}

#[experimental = "waiting on Equiv stabilization"]
impl<'a, S: Str> Equiv<S> for String {
#[inline]
fn equiv(&self, other: &S) -> bool {
self.as_slice() == other.as_slice()
}
}

#[experimental = "waiting on Add stabilization"]
impl<S: Str> Add<S, String> for String {
fn add(&self, other: &S) -> String {
let mut s = String::from_str(self.as_slice());
Expand All @@ -808,6 +875,7 @@ impl<S: Str> Add<S, String> for String {
}

/// Unsafe operations
#[unstable = "waiting on raw module conventions"]
pub mod raw {
use core::mem;
use core::ptr::RawPtr;
Expand Down

0 comments on commit 79b4ce0

Please sign in to comment.