Функция identity

#![allow(unused)]
fn main() {
pub const fn identity<T>(x: T) -> T
}

Функция тождества.

Две важные вещи, которые следует отметить об этой функции:

  • Она не всегда эквивалентна замыканию типа |x| x, поскольку замыкание может приводить x к другому типу.
  • Она перемещает входное значение x, переданное в функцию.

Хотя может показаться странным иметь функцию, которая просто возвращает входные данные, есть некоторые интересные варианты использования.

Примеры

Использование identity для "ничего не делания" в последовательности других, интересных функций:

#![allow(unused)]
fn main() {
use std::convert::identity;

fn manipulation(x: u32) -> u32 {
    // Предположим, что добавление единицы - это интересная функция.
    x + 1
}

let _arr = &[identity, manipulation];
}

Использование identity как базового случая "ничего не делания" в условном выражении:

#![allow(unused)]
fn main() {
use std::convert::identity;

let condition = true; // или false
let do_stuff = if condition { manipulation } else { identity };

// Делаем более интересные вещи...

let results = do_stuff(42);
}

Использование identity для сохранения вариантов Some итератора Option<T>:

#![allow(unused)]
fn main() {
use std::convert::identity;

let iter = [Some(1), None, Some(3)].into_iter();
let filtered = iter.filter_map(identity).collect::<Vec<_>>();
assert_eq!(vec![1, 3], filtered);
}

Использование для преобразования типажа в объект типажа:

#![allow(unused)]
fn main() {
use std::convert::identity;

trait MyTrait {}
struct MyStruct;
impl MyTrait for MyStruct {}

let obj: Box<dyn MyTrait> = Box::new(MyStruct);
// identity может помочь в ситуациях с приведением типов объектов типажей
}

Использование в комбинаторах высшего порядка:

#![allow(unused)]
fn main() {
use std::convert::identity;

let numbers = vec![1, 2, 3, 4, 5];
// Иногда нужно передать функцию-преобразователь, но не менять значения
let unchanged: Vec<i32> = numbers.into_iter().map(identity).collect();
}

Особенности реализации

Константная функция

Функция помечена как const, что позволяет использовать её в константных контекстах:

#![allow(unused)]
fn main() {
const RESULT: i32 = identity(42);
}

Сохранение владения

Функция принимает значение по владению и возвращает его, что полезно для семантики перемещения:

#![allow(unused)]
fn main() {
let s = String::from("hello");
let s2 = identity(s); // s перемещается, а не заимствуется
}

Отсутствие приведения типов

В отличие от замыканий, identity не выполняет неявного приведения типов:

#![allow(unused)]
fn main() {
let x: i32 = 42;
// Замыкание может привести к &i32, а identity - нет
let closure: &i32 = (|x| x)(&x);
// let wrong = identity(&x); // Это не скомпилируется, если ожидается i32
}

Практические сценарии использования

Заполнитель в API

#![allow(unused)]
fn main() {
fn process_data<T, F>(data: T, transformer: F) -> T
where
    F: FnOnce(T) -> T,
{
    transformer(data)
}

// Использование без преобразования
let result = process_data(100, identity);
}

Фильтрация Option значений

#![allow(unused)]
fn main() {
use std::convert::identity;

let options = vec![Some("hello"), None, Some("world")];
let present: Vec<&str> = options.into_iter().filter_map(identity).collect();
}

Базовый случай в рекурсивных алгоритмах

#![allow(unused)]
fn main() {
fn transform_data<F>(data: Vec<i32>, transform: F) -> Vec<i32>
where
    F: Fn(i32) -> i32,
{
    if data.is_empty() {
        vec![]
    } else {
        // Используем identity как преобразование по умолчанию
        data.into_iter().map(transform).collect()
    }
}

// Использование без изменений
let data = vec![1, 2, 3];
let result = transform_data(data, identity);
}

Производительность

Благодаря тому, что функция тривиальна, компилятор обычно полностью оптимизирует её вызовы, не создавая накладных расходов во время выполнения.