Predictability

Enum Predictability 

Source
pub enum Predictability {
    C_SMART_PTR,
    C_CONV_SPECIFIC,
    C_METHOD,
    C_NO_OUT,
    C_OVERLOAD,
    C_DEREF,
    C_CTOR,
}

Variants§

§

C_SMART_PTR

For example, this is why the Box::into_raw function is defined the way it is.

impl<T> Box<T> where T: ?Sized {
    fn into_raw(b: Box<T>) -> *mut T { /* ... */ }
}
let boxed_str: Box<str> = /* ... */;
let ptr = Box::into_raw(boxed_str);
}

If this were defined as an inherent method instead, it would be confusing at the call site whether the method being called is a method on Box or a method on T.

impl<T> Box<T> where T: ?Sized {
    //Do not do this.
    fn into_raw(self) -> *mut T { /* ... */ }
}
let boxed_str: Box<str> = /* ... */;
//This is a method on str accessed through the smart pointer Deref impl.
boxed_str.chars()
//This is a method on Box<str>...?
boxed_str.into_raw()

Smart pointers do not add inherent methods (C-SMART-PTR)

§

C_CONV_SPECIFIC

When in doubt, prefer to_/as_/into_ to from_, because they are more ergonomic to use (and can be chained with other methods).

For many conversions between two types, one of the types is clearly more “specific”: it provides some additional invariant or interpretation that is not present in the other type. For example, str is more specific than &u8, since it is a UTF-8 encoded sequence of bytes.

Conversions should live with the more specific of the involved types. Thus, str provides both the as_bytes method and the from_utf8 constructor for converting to and from &u8 values. Besides being intuitive, this convention avoids polluting concrete types like &u8 with endless conversion methods.

Conversions live on the most specific type involved (C-CONV-SPECIFIC)

§

C_METHOD

for any operation that is clearly associated with a particular type.

Methods have numerous advantages over functions:

  • They do not need to be imported or qualified to be used: all you need is a value of the appropriate type.
  • Their invocation performs autoborrowing (including mutable borrows).
  • They make it easy to answer the question “what can I do with a value of type T” (especially when using rustdoc).
  • They provide self notation, which is more concise and often more clearly conveys ownership distinctions.

Functions with a clear receiver are methods (C-METHOD)

§

C_NO_OUT

Prefer

fn foo() -> (Bar, Bar)

Over

fn foo(output: &mut Bar) -> Bar

for returning multiple Bar values.

Compound return types like tuples and structs are efficiently compiled and do not require heap allocation. If a function needs to return multiple values, it should do so via one of these types.

The primary exception: sometimes a function is meant to modify data that the caller already owns, for example to re-use a buffer:

fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>

Functions do not take out-parameters (C-NO-OUT)

§

C_OVERLOAD

Operators with built in syntax (*, |, and so on) can be provided for a type by implementing the traits in std::ops. These operators come with strong expectations: implement Mul only for an operation that bears some resemblance to multiplication (and shares the expected properties, e.g. associativity), and so on for the other traits.

Operator overloads are unsurprising (C-OVERLOAD)

§

C_DEREF

The Deref traits are used implicitly by the compiler in many circumstances, and interact with method resolution. The relevant rules are designed specifically to accommodate smart pointers, and so the traits should be used only for that purpose.

Examples from the standard library

Only smart pointers implement Deref and DerefMut (C-DEREF)

§

C_CTOR

In Rust, “constructors” are just a convention. There are a variety of conventions around constructor naming, and the distinctions are often subtle.

A constructor in its most basic form is a new method with no arguments.

Constructors are static (no self) inherent methods for the type that they construct. Combined with the practice of fully importing type names, this convention leads to informative but concise construction:

The name new should generally be used for the primary method of instantiating a type. Sometimes it takes no arguments, as in the examples above. Sometimes it does take arguments, like Box::new which is passed the value to place in the Box.

Some types’ constructors, most notably I/O resource types, use distinct naming conventions for their constructors, as in File::open, Mmap::open, TcpStream::connect, and UdpSocket::bind. In these cases names are chosen as appropriate for the domain.

Often there are multiple ways to construct a type. It’s common in these cases for secondary constructors to be suffixed _with_foo, as in Mmap::open_with_offset. If your type has a multiplicity of construction options though, consider the builder pattern (C-BUILDER) instead.

Some constructors are “conversion constructors”, methods that create a new type from an existing value of a different type. These typically have names beginning with from_ as in std::io::Error::from_raw_os_error. Note also though the From trait (C-CONV-TRAITS), which is quite similar. There are three distinctions between a from_-prefixed conversion constructor and a From impl.

  • A from_ constructor can be unsafe; a From impl cannot. One example of this is Box::from_raw.
  • A from_ constructor can accept additional arguments to disambiguate the meaning of the source data, as in u64::from_str_radix.
  • A From impl is only appropriate when the source data type is sufficient to determine the encoding of the output data type. When the input is just a bag of bits like in u64::from_be or String::from_utf8, the conversion constructor name is able to identify their meaning.

Note that it is common and expected for types to implement both Default and a new constructor. For types that have both, they should have the same behavior. Either one may be implemented in terms of the other.

Examples from the standard library

Constructors are static, inherent methods (C-CTOR)

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.