Функция 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); }
Производительность
Благодаря тому, что функция тривиальна, компилятор обычно полностью оптимизирует её вызовы, не создавая накладных расходов во время выполнения.