Hands-On Concurrency with Rust
上QQ阅读APP看书,第一时间看更新

Option

Let's examine Option<T>. We've already discussed Option<T> in this chapter; that it's subject to null pointer optimization on account of its empty None variant in particular. Option is as simple as you might imagine, being defined in src/libcore/option.rs:

#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
#[stable(feature = "rust1", since = "1.0.0")]
pub enum Option<T> {
    /// No value
    #[stable(feature = "rust1", since = "1.0.0")]
    None,
    /// Some value `T`
    #[stable(feature = "rust1", since = "1.0.0")]
    Some(#[stable(feature = "rust1", since = "1.0.0")] T),
}

As is often the case with Rust internals, there are a great deal of flags around to control when and where new features land in which channel and how documentation is generated. A slightly tidier expression of Option<T> is:

#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
pub enum Option<T> {
    None,
    Some(T),
}

This is refreshingly simple, much like how you may have implemented an option type yourself up on first thinking of it. Option is the owner of its inner T and is able to pass out references to that inner data per the usual restrictions. As an example:

    pub fn as_mut(&mut self) -> Option<&mut T> {
        match *self {
            Some(ref mut x) => Some(x),
            None => None,
        }
    }

Rust exposes a special trait type inside each trait: Self. It desugars simply to the referent trait type, in this case, Option<T>. The &mut self is shorthand for self: &mut Self, as is &self for self: &Self . as_mut  is then allocating a new option whose inner type is a mutable reference to the original inner type. Now, consider the humble map:

    pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Option<U> {
        match self {
            Some(x) => Some(f(x)),
            None => None,
        }
    }

Rust allows closures as arguments to functions. In this way, Rust is similar to higher-level and, especially, functional programming languages. Unlike these higher-level programming languages, Rust has restrictions on closures in terms of the way variable capture occurs and with regard to call totals and mutability. Here, we see the FnOnce trait being used, restricting the closure being passed in as f to the map function as being single-use. The function traits, all defined in std::ops, are:

  • Fn
  • FnMut
  • FnOnce

The first trait, Fn, is described by the Rust documentation as being a call operator that takes an immutable receiver. This is maybe a little obscure until we look at the definition of Fn in src/libcore/ops/function.rs:

pub trait Fn<Args>: FnMut<Args> {
    /// Performs the call operation.
    #[unstable(feature = "fn_traits", issue = "29625")]
    extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}

Now it's more obscure! But, we can work that back. Args is distinct from std::env::Args but plays a similar role of being a placeholder for function arguments, save at a type-level. : FnMut<Args> means that the FnMut<Args> is a supertrait of Fn<Args>: all of the methods available to FnMut are available to Fn when used as a trait object. Recall that trait objects find use in dynamic dispatch, discussed previously. This also means that any instance of Fn can be used where an FnMut is expected, in cases of static dispatch. Of particular interest to understanding Fn is:

extern "rust-call" fn call(&self, args: Args) -> Self::Output;

We'll approach this in parts. Firstly, extern "rust-call". Here, we are defining an inline extern block that uses the "rust-call" ABI. Rust supports many ABIs, three of which are cross-platform and guaranteed to be supported no matter the platform:

  • extern "Rust" fn, implicit for all Rust functions unless otherwise specified
  • extern "C" fn, often used in FFI and shorthanded to extern fn 
  • extern "system" fn, equivalent to extern "C" fn save for some special platforms

"rust-call" does not appear in that list because it is a rust-specific ABI, which also includes:

  • extern "rust-intrinsic" fn, specific to rustc intrinsics
  • extern "rust-call" fn, the ABI for all Fn::call functions
  • extern "platform-intrinsic" fn, which the documentation notes as being something the programmer should never have to deal with

Here, we're signalling to the Rust compiler that the function is to be treated with a special call ABI. This particular extern is important when writing traits that implement the Fn trait, as box will when the unstable fnbox feature flag is enabled:

impl<'a, A, R> FnOnce<A> for Box<FnBox<A, Output = R> + 'a> {
    type Output = R;

    extern "rust-call" fn call_once(self, args: A) -> R {
        self.call_box(args)
    }
}

Secondly, fn call(&self, args: Args). The implementing type is taken in as an immutable reference, in addition to the passed args; this is the immutable receiver mentioned in the documentation. The final piece here is -> Self::Output, the returned type after the call operator is used. This associated type defaults to Self but can be set by the implementer.

FnMut is similar to Fn save that it takes &mut self rather than &self, and FnOnce is its supertrait:

pub trait FnMut<Args>: FnOnce<Args> {
    /// Performs the call operation.
    #[unstable(feature = "fn_traits", issue = "29625")]
    extern "rust-call" fn call_mut(&mut self, args: Args) 
-> Self::Output; }

Only FnOnce deviates in its definition:

pub trait FnOnce<Args> {
    /// The returned type after the call operator is used.
    #[stable(feature = "fn_once_output", since = "1.12.0")]
    type Output;

    /// Performs the call operation.
    #[unstable(feature = "fn_traits", issue = "29625")]
    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
}

Here, we see where Self::Output makes its appearance as an associated type and note that the implementations of FnOnce are in terms of call_once rather than call. Also, we now know that FnOnce is a supertrait of FnMut, which is a supertrait of Fn and it just so happens that this property is transitive: if an FnOnce is called for an Fn, it can be used. Much of the exact implementation of the function traits are kept internal to the compiler, the details of which kind of jump around some as internals change. In fact, the "rust-call"extern means that Fn traits cannot be implemented outside of special, compiler-specific contexts; the exact feature flags that need to be enabled in a nightly build, their use, and upcoming changes are not documented. Happily, closures and function pointers implement function traits implicitly. For example:

fn mlen(input: &str) -> usize {
    input.len()
}

fn main() {
    let v: Option<&str> = Some("hope");

    assert_eq!(v.map(|s| s.len()), v.map(mlen));
}

The compiler is good enough to figure out the details for us.

Conceptually, Result<T> is a type similar to Option<T>, save that it is able to communicate an extra piece of information in its Err variant. In fact, the implementation in src/libcore/result.rs is awfully similar to the way we'd likely write this at first though, as with Option:

pub enum Result<T, E> {
    /// Contains the success value
    #[stable(feature = "rust1", since = "1.0.0")]
    Ok(#[stable(feature = "rust1", since = "1.0.0")] T),

    /// Contains the error value
    #[stable(feature = "rust1", since = "1.0.0")]
    Err(#[stable(feature = "rust1", since = "1.0.0")] E),
}