Macro Fizzbuzz

This video came up in my Youtube recommendations: Is My Fizzbuzz Solution Terrible?, which is a follow-up to their previous video One Simple Trick to Instantly Improve Your Code - Finishing Tom Scott's FizzBuzz, which itself is a response to Tom Scott's video FizzBuzz: One Simple Interview Question.

Naturally, that was my instinctive response, so I whipped up something quick.

use :::: as _;

macro_rules!  {
    // 1. The ,* $(,)? incantation at the end allows trailing commas
    ($range:expr, $(($divisor:literal, $replacement:literal)),* $(,)?) => {
        // 2a. Create a new block so the stdout guard is dropped after the loop
        {
            // 2b. Lock standard output once to prevent frequent relocking
            let mut stdout = std::io::stdout().lock();

            // 3a. Reuse underlying string allocation
            let mut s = String::new();
            for i in $range {
                // Macro overloading in action, calling the second macro arm
                $(fizzbuzz!(s, i, $divisor, $replacement);)+

                if s.is_empty() {
                    writeln!(stdout, "{i}")?;
                } else {
                    writeln!(stdout, "{s}")?;
                }

                // 3b. Clear string contents while preserving underlying buffer
                s.clear();
            }
        }
    };

    // The actual remainder comparison
    ($s:ident, $i:ident, $divisor:literal, $replacement:literal) => {
        if $i % $divisor == 0 {
            $s.push_str($replacement);
        }
    };
}

fn () -> <(), <dyn ::::>> {
    !(
        1..101,
        (3, "Fizz"),
        (5, "Buzz"),
        (7, "Bazz"),
        (11, "Bizz"),
        (13, "Biff")
    );

    (())
}

Using cargo-expand, we can examine the expansion of the macro:

#![(prelude_import)]
#[]
use ::::::*;
#[]
extern crate ;
use :::: as _;
fn () -> <(), <dyn ::::>> {
    {
        let mut  = ::::().();
        let mut  = ::();
        for  in 1..101 {
            if  % 3 == 0 {
                .("Fizz");
            }
            if  % 5 == 0 {
                .("Buzz");
            }
            if  % 7 == 0 {
                .("Bazz");
            }
            if  % 11 == 0 {
                .("Bizz");
            }
            if  % 13 == 0 {
                .("Biff");
            }
            if .() {
                .(!("{0}\n", ))?;
            } else {
                .(!("{0}\n", ))?;
            }
            .();
        }
    };
    (())
}

...which is identical to the solution showed by Tom Scott at the end of his video but without the explicit code duplication.

I'll leave implementing Fizzbuzz using macros in a different language as an exercise for the reader.

extern crate std

The Rust Standard Library

The Rust Standard Library is the foundation of portable Rust software, a set of minimal and battle-tested shared abstractions for the broader Rust ecosystem. It offers core types, like [Vec<T>] and [Option<T>], library-defined operations on language primitives, standard macros, [I/O] and [multithreading], among many other things.

std is available to all Rust crates by default. Therefore, the standard library can be accessed in use statements through the path std, as in use std::env.

How to read this documentation

If you already know the name of what you are looking for, the fastest way to find it is to use the search button at the top of the page.

Otherwise, you may want to jump to one of these useful sections:

If this is your first time, the documentation for the standard library is written to be casually perused. Clicking on interesting things should generally lead you to interesting places. Still, there are important bits you don’t want to miss, so read on for a tour of the standard library and its documentation!

Once you are familiar with the contents of the standard library you may begin to find the verbosity of the prose distracting. At this stage in your development you may want to press the “ Summary” button near the top of the page to collapse it into a more skimmable view.

While you are looking at the top of the page, also notice the “Source” link. Rust’s API documentation comes with the source code and you are encouraged to read it. The standard library source is generally high quality and a peek behind the curtains is often enlightening.

What is in the standard library documentation?

First of all, The Rust Standard Library is divided into a number of focused modules, all listed further down this page. These modules are the bedrock upon which all of Rust is forged, and they have mighty names like [std::slice] and [std::cmp]. Modules’ documentation typically includes an overview of the module along with examples, and are a smart place to start familiarizing yourself with the library.

Second, implicit methods on primitive types are documented here. This can be a source of confusion for two reasons:

  1. While primitives are implemented by the compiler, the standard libraryimplements methods directly on the primitive types (and it is the onlylibrary that does so), which are documented in the section on primitives.
  2. The standard library exports many modules with the same name as primitive types. These define additional items related to the primitivetype, but not the all-important methods.

So for example there is a page for the primitive type char that lists all the methods that can be called on characters (very useful), and there is a page for the module std::char that documents iterator and error types created by these methods (rarely useful).

Note the documentation for the primitives [str] and [T] (also called ‘slice’). Many method calls on String and [Vec<T>] are actually calls to methods on [str] and [T] respectively, via deref coercions.

Third, the standard library defines [The Rust Prelude], a small collection of items - mostly traits - that are imported into every module of every crate. The traits in the prelude are pervasive, making the prelude documentation a good entry point to learning about the library.

And finally, the standard library exports a number of standard macros, and lists them on this page (technically, not all of the standard macros are defined by the standard library - some are defined by the compiler - but they are documented here the same). Like the prelude, the standard macros are imported by default into all crates.

Contributing changes to the documentation

Check out the Rust contribution guidelines here. The source for this documentation can be found on GitHub in the ‘library/std/’ directory. To contribute changes, make sure you read the guidelines first, then submit pull-requests for your suggested changes.

Contributions are appreciated! If you see a part of the docs that can be improved, submit a PR, or chat with us first on Zulip #docs.

A Tour of The Rust Standard Library

The rest of this crate documentation is dedicated to pointing out notable features of The Rust Standard Library.

Containers and collections

The option and result modules define optional and error-handling types, [Option<T>] and [Result<T, E>]. The iter module defines Rust’s iterator trait, Iterator, which works with the for loop to access collections.

The standard library exposes three common ways to deal with contiguous regions of memory:

  • [Vec<T>] - A heap-allocated vector that is resizable at runtime.
  • [T; N] - An inline array with a fixed size at compile time.
  • [T] - A dynamically sized slice into any other kind of contiguousstorage, whether heap-allocated or not.

Slices can only be handled through some kind of pointer, and as such come in many flavors such as:

  • &[T] - shared slice
  • &mut [T] - mutable slice
  • Box<[T]> - owned slice

[str], a UTF-8 string slice, is a primitive type, and the standard library defines many methods for it. Rust [str]s are typically accessed as immutable references: &str. Use the owned String for building and mutating strings.

For converting to strings use the format macro, and for converting from strings use the [FromStr] trait.

Data may be shared by placing it in a reference-counted box or the [Rc] type, and if further contained in a [Cell] or [RefCell], may be mutated as well as shared. Likewise, in a concurrent setting it is common to pair an atomically-reference-counted box, [Arc], with a [Mutex] to get the same effect.

The collections module defines maps, sets, linked lists and other typical collection types, including the common [HashMap<K, V>].

Platform abstractions and I/O

Besides basic data types, the standard library is largely concerned with abstracting over differences in common platforms, most notably Windows and Unix derivatives.

Common types of I/O, including [files], [TCP], and [UDP], are defined in the io, fs, and net modules.

The thread module contains Rust’s threading abstractions. sync contains further primitive shared memory types, including [atomic], [mpmc] and [mpsc], which contains the channel types for message passing.

Use before and after main()

Many parts of the standard library are expected to work before and after main(); but this is not guaranteed or ensured by tests. It is recommended that you write your own tests and run them on each platform you wish to support. This means that use of std before/after main, especially of features that interact with the OS or global state, is exempted from stability and portability guarantees and instead only provided on a best-effort basis. Nevertheless bug reports are appreciated.

On the other hand core and alloc are most likely to work in such environments with the caveat that any hookable behavior such as panics, oom handling or allocators will also depend on the compatibility of the hooks.

Some features may also behave differently outside main, e.g. stdio could become unbuffered, some panics might turn into aborts, backtraces might not get symbolicated or similar.

Non-exhaustive list of known limitations:

  • after-main use of thread-locals, which also affects additional features:
  • under UNIX, before main, file descriptors 0, 1, and 2 may be unchanged(they are guaranteed to be open during main,and are opened to /dev/null O_RDWR if they weren’t open on program start)
std
pub mod io

Traits, helpers, and type definitions for core I/O functionality.

The std::io module contains a number of common things you’ll need when doing input and output. The most core part of this module is the Read and Write traits, which provide the most general interface for reading and writing input and output.

Read and Write

Because they are traits, Read and Write are implemented by a number of other types, and you can implement them for your types too. As such, you’ll see a few different types of I/O throughout the documentation in this module: [File]s, [TcpStream]s, and sometimes even Vec<T>s. For example, Read adds a read method, which we can use on [File]s:

use std::io;
use std::io::prelude::*;
use std::fs::File;

fn main() -> io::Result<()> {
    let mut f = File::open("foo.txt")?;
    let mut buffer = [0; 10];

    // read up to 10 bytes
    let n = f.read(&mut buffer)?;

    println!("The bytes: {:?}", &buffer[..n]);
    Ok(())
}

Read and Write are so important, implementors of the two traits have a nickname: readers and writers. So you’ll sometimes see ‘a reader’ instead of ‘a type that implements the Read trait’. Much easier!

Seek and BufRead

Beyond that, there are two important traits that are provided: Seek and BufRead. Both of these build on top of a reader to control how the reading happens. Seek lets you control where the next byte is coming from:

use std::io;
use std::io::prelude::*;
use std::io::SeekFrom;
use std::fs::File;

fn main() -> io::Result<()> {
    let mut f = File::open("foo.txt")?;
    let mut buffer = [0; 10];

    // skip to the last 10 bytes of the file
    f.seek(SeekFrom::End(-10))?;

    // read up to 10 bytes
    let n = f.read(&mut buffer)?;

    println!("The bytes: {:?}", &buffer[..n]);
    Ok(())
}

BufRead uses an internal buffer to provide a number of other ways to read, but to show it off, we’ll need to talk about buffers in general. Keep reading!

BufReader and BufWriter

Byte-based interfaces are unwieldy and can be inefficient, as we’d need to be making near-constant calls to the operating system. To help with this, std::io comes with two structs, BufReader and BufWriter, which wrap readers and writers. The wrapper uses a buffer, reducing the number of calls and providing nicer methods for accessing exactly what you want.

For example, BufReader works with the BufRead trait to add extra methods to any reader:

use std::io;
use std::io::prelude::*;
use std::io::BufReader;
use std::fs::File;

fn main() -> io::Result<()> {
    let f = File::open("foo.txt")?;
    let mut reader = BufReader::new(f);
    let mut buffer = String::new();

    // read a line into buffer
    reader.read_line(&mut buffer)?;

    println!("{buffer}");
    Ok(())
}

BufWriter doesn’t add any new ways of writing; it just buffers every call to write:

use std::io;
use std::io::prelude::*;
use std::io::BufWriter;
use std::fs::File;

fn main() -> io::Result<()> {
    let f = File::create("foo.txt")?;
    {
        let mut writer = BufWriter::new(f);

        // write a byte to the buffer
        writer.write(&[42])?;

    } // the buffer is flushed once writer goes out of scope

    Ok(())
}

Standard input and output

A very common source of input is standard input:

use std::io;

fn main() -> io::Result<()> {
    let mut input = String::new();

    io::stdin().read_line(&mut input)?;

    println!("You typed: {}", input.trim());
    Ok(())
}

Note that you cannot use the ? operator in functions that do not return a Result<T, E>. Instead, you can call [.unwrap()] or match on the return value to catch any possible errors:

use std::io;

let mut input = String::new();

io::stdin().read_line(&mut input).unwrap();

And a very common source of output is standard output:

use std::io;
use std::io::prelude::*;

fn main() -> io::Result<()> {
    io::stdout().write(&[42])?;
    Ok(())
}

Of course, using [io::stdout] directly is less common than something like println.

Iterator types

A large number of the structures provided by std::io are for various ways of iterating over I/O. For example, Lines is used to split over lines:

use std::io;
use std::io::prelude::*;
use std::io::BufReader;
use std::fs::File;

fn main() -> io::Result<()> {
    let f = File::open("foo.txt")?;
    let reader = BufReader::new(f);

    for line in reader.lines() {
        println!("{}", line?);
    }
    Ok(())
}

Functions

There are a number of functions that offer access to various features. For example, we can use three of these functions to copy everything from standard input to standard output:

use std::io;

fn main() -> io::Result<()> {
    io::copy(&mut io::stdin(), &mut io::stdout())?;
    Ok(())
}

io::Result

Last, but certainly not least, is [io::Result]. This type is used as the return type of many std::io functions that can cause an error, and can be returned from your own functions as well. Many of the examples in this module use the ? operator:

use std::io;

fn read_input() -> io::Result<()> {
    let mut input = String::new();

    io::stdin().read_line(&mut input)?;

    println!("You typed: {}", input.trim());

    Ok(())
}

The return type of read_input(), io::Result<()>, is a very common type for functions which don’t have a ‘real’ return value, but do want to return errors if they happen. In this case, the only purpose of this function is to read the line and print it, so we use ().

Platform-specific behavior

Many I/O functions throughout the standard library are documented to indicate what various library or syscalls they are delegated to. This is done to help applications both understand what’s happening under the hood as well as investigate any possibly unclear semantics. Note, however, that this is informative, not a binding contract. The implementation of many of these functions are subject to change over time and may call fewer or more syscalls/library functions.

I/O Safety

Rust follows an I/O safety discipline that is comparable to its memory safety discipline. This means that file descriptors can be exclusively owned. (Here, “file descriptor” is meant to subsume similar concepts that exist across a wide range of operating systems even if they might use a different name, such as “handle”.) An exclusively owned file descriptor is one that no other code is allowed to access in any way, but the owner is allowed to access and even close it any time. A type that owns its file descriptor should usually close it in its drop function. Types like [File] own their file descriptor. Similarly, file descriptors can be borrowed, granting the temporary right to perform operations on this file descriptor. This indicates that the file descriptor will not be closed for the lifetime of the borrow, but it does not imply any right to close this file descriptor, since it will likely be owned by someone else.

The platform-specific parts of the Rust standard library expose types that reflect these concepts, see os::unix and os::windows.

To uphold I/O safety, it is crucial that no code acts on file descriptors it does not own or borrow, and no code closes file descriptors it does not own. In other words, a safe function that takes a regular integer, treats it as a file descriptor, and acts on it, is unsound.

Not upholding I/O safety and acting on a file descriptor without proof of ownership can lead to misbehavior and even Undefined Behavior in code that relies on ownership of its file descriptors: a closed file descriptor could be re-allocated, so the original owner of that file descriptor is now working on the wrong file. Some code might even rely on fully encapsulating its file descriptors with no operations being performed by any other part of the program.

Note that exclusive ownership of a file descriptor does not imply exclusive ownership of the underlying kernel object that the file descriptor references (also called “open file description” on some operating systems). File descriptors basically work like [Arc]: when you receive an owned file descriptor, you cannot know whether there are any other file descriptors that reference the same kernel object. However, when you create a new kernel object, you know that you are holding the only reference to it. Just be careful not to lend it to anyone, since they can obtain a clone and then you can no longer know what the reference count is! In that sense, OwnedFd is like Arc and BorrowedFd<'a> is like &'a Arc (and similar for the Windows types). In particular, given a BorrowedFd<'a>, you are not allowed to close the file descriptor – just like how, given a &'a Arc, you are not allowed to decrement the reference count and potentially free the underlying object. There is no equivalent to Box for file descriptors in the standard library (that would be a type that guarantees that the reference count is 1), however, it would be possible for a crate to define a type with those semantics.

std::io
pub trait Write

A trait for objects which are byte-oriented sinks.

Implementors of the Write trait are sometimes called ‘writers’.

Writers are defined by two required methods, [write] and [flush]:

  • The [write] method will attempt to write some data into the object, returning how many bytes were successfully written.

  • The [flush] method is useful for adapters and explicit buffers themselves for ensuring that all buffered data has been pushed out to the ‘true sink’.

Writers are intended to be composable with one another. Many implementors throughout [std::io] take and provide types which implement the Write trait.

Examples

use std::io::prelude::*;
use std::fs::File;

fn main() -> std::io::Result<()> {
    let data = b"some bytes";

    let mut pos = 0;
    let mut buffer = File::create("foo.txt")?;

    while pos < data.len() {
        let bytes_written = buffer.write(&data[pos..])?;
        pos += bytes_written;
    }
    Ok(())
}

The trait also provides convenience methods like [write_all], which calls write in a loop until its entire input has been written.

codeintel::block_58815316d015966d
macro_rules! fizzbuzz
codeintel::block_58815316d015966d
fn main() -> Result<(), Box<dyn std::error::Error>>
core::result
pub enum Result<T, E> {
    Ok( /* … */ ),
    Err( /* … */ ),
}

Result is a type that represents either success (Ok) or failure (Err).

See the module documentation for details.

alloc::boxed
pub struct Box<T, A = Global>(Unique<T>, A)
where
    T: ?Sized,
    A: Allocator,

A pointer type that uniquely owns a heap allocation of type T.

See the module-level documentation for more.

std
pub mod error
core::error
pub trait Error
where
    Self: Debug + Display,

Error is a trait representing the basic expectations for error values, i.e., values of type E in Result<T, E>.

Errors must describe themselves through the Display and Debug traits. Error messages are typically concise lowercase sentences without trailing punctuation:

let err = "NaN".parse::<u32>().unwrap_err();
assert_eq!(err.to_string(), "invalid digit found in string");

Error source

Errors may provide cause information. Error::source is generally used when errors cross “abstraction boundaries”. If one module must report an error that is caused by an error from a lower-level module, it can allow accessing that error via Error::source(). This makes it possible for the high-level module to provide its own errors while also revealing some of the implementation for debugging.

In error types that wrap an underlying error, the underlying error should be either returned by the outer error’s Error::source(), or rendered by the outer error’s Display implementation, but not both.

Example

Implementing the Error trait only requires that Debug and Display are implemented too.

use std::error::Error;
use std::fmt;
use std::path::PathBuf;

#[derive(Debug)]
struct ReadConfigError {
    path: PathBuf
}

impl fmt::Display for ReadConfigError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let path = self.path.display();
        write!(f, "unable to read configuration at {path}")
    }
}

impl Error for ReadConfigError {}
core::result::Result
Ok(T)

Contains the success value

#[feature]

Valid forms are:

  • #[feature(name1, name2, …)]
#[prelude_import]

Valid forms are:

  • #[prelude_import]
std
pub mod prelude

The Rust Prelude

Rust comes with a variety of things in its standard library. However, if you had to manually import every single thing that you used, it would be very verbose. But importing a lot of things that a program never uses isn’t good either. A balance needs to be struck.

The prelude is the list of things that Rust automatically imports into every Rust program. It’s kept as small as possible, and is focused on things, particularly traits, which are used in almost every single Rust program.

Other preludes

Preludes can be seen as a pattern to make using multiple types more convenient. As such, you’ll find other preludes in the standard library, such as [std::io::prelude]. Various libraries in the Rust ecosystem may also define their own preludes.

The difference between ‘the prelude’ and these other preludes is that they are not automatically use’d, and must be imported manually. This is still easier than importing all of their constituent components.

Prelude contents

The items included in the prelude depend on the edition of the crate. The first version of the prelude is used in Rust 2015 and Rust 2018, and lives in [std::prelude::v1]. [std::prelude::rust_2015] and [std::prelude::rust_2018] re-export this prelude. It re-exports the following:

[std::marker]::{Copy, Send, Sized, Sync, Unpin}, marker traits that indicate fundamental properties of types.

[std::ops]::{Fn, FnMut, FnOnce}, and their analogous async traits, [std::ops]::{AsyncFn, AsyncFnMut, AsyncFnOnce}.

[std::ops]::Drop, for implementing destructors.

[std::mem]::drop, a convenience function for explicitly dropping a value.

[std::mem]::{size_of, size_of_val}, to get the size of a type or value.

[std::mem]::{align_of, align_of_val}, to get the alignment of a type or value.

[std::boxed]::Box, a way to allocate values on the heap.

[std::borrow]::ToOwned, the conversion trait that defines [to_owned], the generic method for creating an owned type from a borrowed type.

[std::clone]::Clone, the ubiquitous trait that defines clone, the method for producing a copy of a value.

[std::cmp]::{PartialEq, PartialOrd, Eq, Ord}, the comparison traits, which implement the comparison operators and are often seen in trait bounds.

[std::convert]::{AsRef, AsMut, Into, From}, generic conversions, used by savvy API authors to create overloaded methods.

[std::default]::Default, types that have default values.

std::iter::{Iterator, Extend, IntoIterator, DoubleEndedIterator, ExactSizeIterator}, iterators of various kinds.

[std::option]::Option::{self, Some, None}, a type which expresses the presence or absence of a value. This type is so commonly used, its variants are also exported.

[std::result]::Result::{self, Ok, Err}, a type for functions that may succeed or fail. Like Option, its variants are exported as well.

[std::string]::{String, ToString}, heap-allocated strings.

[std::vec]::Vec, a growable, heap-allocated vector.

The prelude used in Rust 2021, [std::prelude::rust_2021], includes all of the above, and in addition re-exports:

[std::convert]::{TryFrom, TryInto}.

The prelude used in Rust 2024, [std::prelude::rust_2024], includes all of the above, and in addition re-exports:

[std::future]::{[Future], [IntoFuture]}.

std::prelude
pub mod rust_2021

The 2021 version of the prelude of The Rust Standard Library.

See the module-level documentation for more.

#[macro_use]

Valid forms are:

  • #[macro_use]
  • #[macro_use(name1, name2, …)]
codeintel::block_351821bf96bde16e
fn main() -> Result<(), Box<dyn std::error::Error>>
let mut stdout: StdoutLock<'_>
std::io::stdio
pub fn stdout() -> Stdout

Constructs a new handle to the standard output of the current process.

Each handle returned is a reference to a shared global buffer whose access is synchronized via a mutex. If you need more explicit control over locking, see the Stdout::lock method.

By default, the handle is line-buffered when connected to a terminal, meaning it flushes automatically when a newline (\n) is encountered. For immediate output, you can manually call the [flush] method. When the handle goes out of scope, the buffer is automatically flushed.

Note: Windows Portability Considerations

When operating in a console, the Windows implementation of this stream does not support non-UTF-8 byte sequences. Attempting to write bytes that are not valid UTF-8 will return an error.

In a process with a detached console, such as one using #![windows_subsystem = "windows"], or in a child process spawned from such a process, the contained handle will be null. In such cases, the standard library’s Read and Write will do nothing and silently succeed. All other I/O operations, via the standard library or via raw Windows API calls, will fail.

Examples

Using implicit synchronization:

use std::io::{self, Write};

fn main() -> io::Result<()> {
    io::stdout().write_all(b"hello world")?;

    Ok(())
}

Using explicit synchronization:

use std::io::{self, Write};

fn main() -> io::Result<()> {
    let stdout = io::stdout();
    let mut handle = stdout.lock();

    handle.write_all(b"hello world")?;

    Ok(())
}

Ensuring output is flushed immediately:

use std::io::{self, Write};

fn main() -> io::Result<()> {
    let mut stdout = io::stdout();
    stdout.write_all(b"hello, ")?;
    stdout.flush()?;                // Manual flush
    stdout.write_all(b"world!\n")?; // Automatically flushed
    Ok(())
}
std::io::stdio::Stdout
pub fn lock(&self) -> StdoutLock<'static>

Locks this handle to the standard output stream, returning a writable guard.

The lock is released when the returned lock goes out of scope. The returned guard also implements the Write trait for writing data.

Examples

use std::io::{self, Write};

fn main() -> io::Result<()> {
    let mut stdout = io::stdout().lock();

    stdout.write_all(b"hello world")?;

    Ok(())
}
let mut s: String
alloc::string
pub struct String {
    vec: Vec<u8>,
}

A UTF-8–encoded, growable string.

String is the most common string type. It has ownership over the contents of the string, stored in a heap-allocated buffer (see Representation). It is closely related to its borrowed counterpart, the primitive [str].

Examples

You can create a String from a literal string with [String::from]:

let hello = String::from("Hello, world!");

You can append a char to a String with the [push] method, and append a [&str] with the [push_str] method:

let mut hello = String::from("Hello, ");

hello.push('w');
hello.push_str("orld!");

If you have a vector of UTF-8 bytes, you can create a String from it with the [from_utf8] method:

// some bytes, in a vector
let sparkle_heart = vec![240, 159, 146, 150];

// We know these bytes are valid, so we'll use `unwrap()`.
let sparkle_heart = String::from_utf8(sparkle_heart).unwrap();

assert_eq!("💖", sparkle_heart);

UTF-8

Strings are always valid UTF-8. If you need a non-UTF-8 string, consider OsString. It is similar, but without the UTF-8 constraint. Because UTF-8 is a variable width encoding, Strings are typically smaller than an array of the same chars:

// `s` is ASCII which represents each `char` as one byte
let s = "hello";
assert_eq!(s.len(), 5);

// A `char` array with the same contents would be longer because
// every `char` is four bytes
let s = ['h', 'e', 'l', 'l', 'o'];
let size: usize = s.into_iter().map(|c| size_of_val(&c)).sum();
assert_eq!(size, 20);

// However, for non-ASCII strings, the difference will be smaller
// and sometimes they are the same
let s = "💖💖💖💖💖";
assert_eq!(s.len(), 20);

let s = ['💖', '💖', '💖', '💖', '💖'];
let size: usize = s.into_iter().map(|c| size_of_val(&c)).sum();
assert_eq!(size, 20);

This raises interesting questions as to how s[i] should work. What should i be here? Several options include byte indices and char indices but, because of UTF-8 encoding, only byte indices would provide constant time indexing. Getting the ith char, for example, is available using [chars]:

let s = "hello";
let third_character = s.chars().nth(2);
assert_eq!(third_character, Some('l'));

let s = "💖💖💖💖💖";
let third_character = s.chars().nth(2);
assert_eq!(third_character, Some('💖'));

Next, what should s[i] return? Because indexing returns a reference to underlying data it could be &u8, &[u8], or something similar. Since we’re only providing one index, &u8 makes the most sense but that might not be what the user expects and can be explicitly achieved with [as_bytes()]:

// The first byte is 104 - the byte value of `'h'`
let s = "hello";
assert_eq!(s.as_bytes()[0], 104);
// or
assert_eq!(s.as_bytes()[0], b'h');

// The first byte is 240 which isn't obviously useful
let s = "💖💖💖💖💖";
assert_eq!(s.as_bytes()[0], 240);

Due to these ambiguities/restrictions, indexing with a usize is simply forbidden:

let s = "hello";

// The following will not compile!
println!("The first letter of s is {}", s[0]);

It is more clear, however, how &s[i..j] should work (that is, indexing with a range). It should accept byte indices (to be constant-time) and return a &str which is UTF-8 encoded. This is also called “string slicing”. Note this will panic if the byte indices provided are not character boundaries - see [is_char_boundary] for more details. See the implementations for [SliceIndex<str>] for more details on string slicing. For a non-panicking version of string slicing, see [get].

The [bytes] and [chars] methods return iterators over the bytes and codepoints of the string, respectively. To iterate over codepoints along with byte indices, use [char_indices].

Deref

String implements [Deref]<Target = [str]>, and so inherits all of [str]’s methods. In addition, this means that you can pass a String to a function which takes a [&str] by using an ampersand (&):

fn takes_str(s: &str) { }

let s = String::from("Hello");

takes_str(&s);

This will create a [&str] from the String and pass it in. This conversion is very inexpensive, and so generally, functions will accept [&str]s as arguments unless they need a String for some specific reason.

In certain cases Rust doesn’t have enough information to make this conversion, known as [Deref] coercion. In the following example a string slice &'a str implements the trait TraitExample, and the function example_func takes anything that implements the trait. In this case Rust would need to make two implicit conversions, which Rust doesn’t have the means to do. For that reason, the following example will not compile.

trait TraitExample {}

impl<'a> TraitExample for &'a str {}

fn example_func<A: TraitExample>(example_arg: A) {}

let example_string = String::from("example_string");
example_func(&example_string);

There are two options that would work instead. The first would be to change the line example_func(&example_string); to example_func(example_string.as_str());, using the method [as_str()] to explicitly extract the string slice containing the string. The second way changes example_func(&example_string); to example_func(&*example_string);. In this case we are dereferencing a String to a [str], then referencing the [str] back to [&str]. The second way is more idiomatic, however both work to do the conversion explicitly rather than relying on the implicit conversion.

Representation

A String is made up of three components: a pointer to some bytes, a length, and a capacity. The pointer points to the internal buffer which String uses to store its data. The length is the number of bytes currently stored in the buffer, and the capacity is the size of the buffer in bytes. As such, the length will always be less than or equal to the capacity.

This buffer is always stored on the heap.

You can look at these with the [as_ptr], [len], and [capacity] methods:

let story = String::from("Once upon a time...");

// Deconstruct the String into parts.
let (ptr, len, capacity) = story.into_raw_parts();

// story has nineteen bytes
assert_eq!(19, len);

// We can re-build a String out of ptr, len, and capacity. This is all
// unsafe because we are responsible for making sure the components are
// valid:
let s = unsafe { String::from_raw_parts(ptr, len, capacity) } ;

assert_eq!(String::from("Once upon a time..."), s);

If a String has enough capacity, adding elements to it will not re-allocate. For example, consider this program:

let mut s = String::new();

println!("{}", s.capacity());

for _ in 0..5 {
    s.push_str("hello");
    println!("{}", s.capacity());
}

This will output the following:

0
8
16
16
32
32

At first, we have no memory allocated at all, but as we append to the string, it increases its capacity appropriately. If we instead use the [with_capacity] method to allocate the correct capacity initially:

let mut s = String::with_capacity(25);

println!("{}", s.capacity());

for _ in 0..5 {
    s.push_str("hello");
    println!("{}", s.capacity());
}

We end up with a different output:

25
25
25
25
25
25

Here, there’s no need to allocate more memory inside the loop.

alloc::string::String
pub const fn new() -> String

Creates a new empty String.

Given that the String is empty, this will not allocate any initial buffer. While that means that this initial operation is very inexpensive, it may cause excessive allocation later when you add data. If you have an idea of how much data the String will hold, consider the [with_capacity] method to prevent excessive re-allocation.

Examples

let s = String::new();
let i: i32
alloc::string::String
pub fn push_str(&mut self, string: &str)

Appends a given string slice onto the end of this String.

Examples

let mut s = String::from("foo");

s.push_str("bar");

assert_eq!("foobar", s);
alloc::string::String
pub const fn is_empty(&self) -> bool

Returns true if this String has a length of zero, and false otherwise.

Examples

let mut v = String::new();
assert!(v.is_empty());

v.push('a');
assert!(!v.is_empty());
std::io::Write
pub trait Write
pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> Result<()>

Writes a formatted string into this writer, returning any error encountered.

This method is primarily used to interface with the format_args!() macro, and it is rare that this should explicitly be called. The write!() macro should be favored to invoke this method instead.

This function internally uses the [write_all] method on this trait and hence will continuously write data so long as no errors are received. This also means that partial writes are not indicated in this signature.

Errors

This function will return any I/O error reported while formatting.

Examples

use std::io::prelude::*;
use std::fs::File;

fn main() -> std::io::Result<()> {
    let mut buffer = File::create("foo.txt")?;

    // this call
    write!(buffer, "{:.*}", 2, 1.234567)?;
    // turns into this:
    buffer.write_fmt(format_args!("{:.*}", 2, 1.234567))?;
    Ok(())
}
core::macros::builtin
macro_rules! format_args

Constructs parameters for the other string-formatting macros.

This macro functions by taking a formatting string literal containing {} for each additional argument passed. format_args! prepares the additional parameters to ensure the output can be interpreted as a string and canonicalizes the arguments into a single type. Any value that implements the [Display] trait can be passed to format_args!, as can any [Debug] implementation be passed to a {:?} within the formatting string.

This macro produces a value of type [fmt::Arguments]. This value can be passed to the macros within std::fmt for performing useful redirection. All other formatting macros (format!, write, println!, etc) are proxied through this one. format_args!, unlike its derived macros, avoids heap allocations.

You can use the [fmt::Arguments] value that format_args! returns in Debug and Display contexts as seen below. The example also shows that Debug and Display format to the same thing: the interpolated format string in format_args!.

let args = format_args!("{} foo {:?}", 1, 2);
let debug = format!("{args:?}");
let display = format!("{args}");
assert_eq!("1 foo 2", display);
assert_eq!(display, debug);

See the formatting documentation in std::fmt for details of the macro argument syntax, and further information.

Examples

use std::fmt;

let s = fmt::format(format_args!("hello {}", "world"));
assert_eq!(s, format!("hello {}", "world"));

Argument lifetimes

Except when no formatting arguments are used, the produced fmt::Arguments value borrows temporary values. To allow it to be stored for later use, the arguments’ lifetimes, as well as those of temporaries they borrow, may be extended when format_args! appears in the initializer expression of a let statement. The syntactic rules used to determine when temporaries’ lifetimes are extended are documented in the Reference.

alloc::string::String
pub fn clear(&mut self)

Truncates this String, removing all contents.

While this means the String will have a length of zero, it does not touch its capacity.

Examples

let mut s = String::from("foo");

s.clear();

assert!(s.is_empty());
assert_eq!(0, s.len());
assert_eq!(3, s.capacity());