libs (conversions)
convert_identity
Adds an identity function pub const fn identity<T>(x: T) -> T { x }
as core::convert::identity
. The function is also re-exported to
std::convert::identity
.
While it might seem strange to have a function that just returns back the input, there are some cases where the function is useful.
identity
to do nothing among a collection of mappersWhen you have collections such as maps or arrays of mapping functions like below and you watch to dispatch to those you sometimes need the identity function as a way of not transforming the input. You can use the identity function to achieve this.
// Let's assume that this and other functions do something non-trivial.
fn do_interesting_stuff(x: u32) -> u32 { .. }
// A dispatch-map of mapping functions:
let mut map = HashMap::new();
map.insert("foo", do_interesting_stuff);
map.insert("bar", other_stuff);
map.insert("baz", identity);
identity
as a no-op function in a conditionalThis reasoning also applies to simpler yes/no dispatch as below:
let mapper = if condition { some_manipulation } else { identity };
// do more interesting stuff inbetween..
do_stuff(42);
identity
to concatenate an iterator of iteratorsWe can use the identity function to concatenate an iterator of iterators into a single iterator.
let vec_vec = vec![vec![1, 3, 4], vec![5, 6]];
let iter_iter = vec_vec.into_iter().map(Vec::into_iter);
let concatenated = iter_iter.flat_map(identity).collect::<Vec<_>>();
assert_eq!(vec![1, 3, 4, 5, 6], concatenated);
While the standard library has recently added Iterator::flatten
,
which you should use instead, to achieve the same semantics, similar situations
are likely in the wild and the identity
function can be used in those cases.
identity
to keep the Some
variants of an iterator of Option<T>
We can keep all the maybe variants by simply iter.filter_map(identity)
.
let iter = vec![Some(1), None, Some(3)].into_iter();
let filtered = iter.filter_map(identity).collect::<Vec<_>>();
assert_eq!(vec![1, 3], filtered);
If you instead use a closure as in |x| x
when you need an
identity conversion, it is less clear that this was intentional.
With identity
, this intent becomes clearer.
drop
function as a precedentThe drop
function in core::mem
is defined as pub fn drop<T>(_x: T) { }
.
The same effect can be achieved by writing { _x; }
. This presents us
with a precendent that such trivial functions are considered useful and
includable inside the standard library even though they can be written easily
inside a user's crate.
Here are a few examples of the identity function being defined and used:
There's a smattering of more examples. To reduce duplication, it should be provided in the standard library as a common place it is defined.
There are other languages that include an identity function in their standard libraries, among these are:
An identity function is a mapping of one type onto itself such that the output
is the same as the input. In other words, a function identity : T -> T
for
some type T
defined as identity(x) = x
. This RFC adds such a function for
all Sized
types in Rust into libcore at the module core::convert
and
defines it as:
pub const fn identity<T>(x: T) -> T { x }
This function is also re-exported to std::convert::identity
.
It is important to note that the input x
passed to the function is
moved since Rust uses move semantics by default.
An identity function defined as pub const fn identity<T>(x: T) -> T { x }
exists as core::convert::identity
. The function is also re-exported as
std::convert::identity
-
Note that the identity function is not always equivalent to a closure
such as |x| x
since the closure may coerce x
into a different type
while the identity function never changes the type.
It is already possible to do this in user code by:
|x| x
.identity
function as defined in the RFC yourself.These are contrasted with the motivation for including the function in the standard library.
The rationale for including this in convert
and not mem
is that the
former generally deals with conversions, and identity conversion" is a used
phrase. Meanwhile, mem
does not relate to identity
other than that both
deal with move semantics. Therefore, convert
is the better choice. Including
it in mem
is still an alternative, but as explained, it isn't fitting.
Naming the function id
instead of identity
is a possibility.
This name is however ambiguous with "identifier" and less clear
wherefore identifier
was opted for.
There are no unresolved questions.
A previous iteration of this RFC proposed that the identity
function
should be added to prelude of both libcore and libstd.
However, the library team decided that for the time being, it was not sold on
this inclusion. As we gain usage experience with using this function,
it is possible to revisit this in the future if the team chances its mind.
The section below details, for posterity, the argument for inclusion that was previously in the motivation.
Let's compare the effort required, assuming that each letter typed has a uniform cost with respect to effort.
use std::convert::identity; iter.filter_map(identity)
fn identity<T>(x: T) -> T { x } iter.filter_map(identity)
iter.filter_map(::std::convert::identity)
iter.filter_map(identity)
Comparing the length of these lines, we see that there's not much difference in length when defining the function yourself or when importing or using an absolute path. But the prelude-using variant is considerably shorter. To encourage the use of the function, exporting to the prelude is therefore a good idea.
In addition, there's an argument to be made from similarity to other things in
core::convert
as well as drop
all of which are in the prelude. This is
especially relevant in the case of drop
which is also a trivial function.