Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Тема
Описание
Доп.

📌 Новый раздел

  • 👾 Best practice
  • Варианты применения

Cow — очень удобное перечисление. Это означает «клонировать при записи» и позволяет вам вернуть, &str если вам не нужен String, и a String, если он вам нужен. То же самое можно делать и с массивами, с векторами и т. д.


fn main(){
    let s:Cow<'static, str> = "...".into();
    let s:Cow<'static, str> = Cow::from("...");
    let s:Cow<'static, str> = String::from("...").into();
    let s:Cow<'static, str> = Cow::from(String::from("..."));
    let arr:Cow<'static, [u8]> =  [1u8;0][..].into();
    let arr:Cow<'static, [u8]> = vec![1u8;0].into();
    let arr:Cow<'static, [u8]> =  [1u8;0][..].into();
    let arr:Cow<'static, [u8]> = Cow::from(&[1u8;0][..]);
}

Применение

  • Текстовый редактор, вектор строк. Обычно строки читают, обычно их много. Изредка иногда некоторые меняют, тогда &str на месте под Cow апгрейдится в String

  • Считали файл как str, побили его строки на &str, положили их в вектор. Затем пользователь дописывает в строку посередине файла. Без аллокации его новое содержимое уже не поместится в исходный &str.

std::borrow::Cow::Borrowed(&'a B) — Ссылка на некоторый объект типа B, при этом время жизни контейнера точно такое же, как у связанного с ним значения B.

std::borrow::Cow::Owned(B::Owned) — Контейнер владеет значением ассоциированного типа B::Owned

  1. Cow позволяет объединить использование собственных и заимствованных данных в одной абстракции, что приводит к улучшению эргономики и максимальному минимизации потерь производительности;

  2. Cow включает и обеспечивает неизменяемый доступ к заимствованным данным, а также лениво клонирует данные, когда требуется изменение или владение.

При создании копии контейнера, реальные данные не копируются, реальное же разделение производится лишь при попытке изменить значение, хранящееся внутри контейнера.

Если данные меняются, происходит клонирование на месте, иначе данных хранятся по ссылке.

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

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

Методы std::borrow::Cow

enum.Cow.html#method.to_mut

into_owned() - Извлекает имеющиеся данные.

is_borrowed - Возвращает true, если данные заимствованы, т.е. если to_mut потребует дополнительной работы. nightly

is_owned() - Возвращает true, если данные принадлежат пользователю, т. е. если to_mut не будет операцией no-op. nightly

to_mut() - Получает изменяемую ссылку на принадлежащую ему форму данных. Клонирует данных, если они еще не принадлежат ему.


use std::borrow::Cow;
fn main(){
    let mut cow = Cow::Borrowed("foo");
    cow.to_mut().make_ascii_uppercase();
    assert_eq!( cow, Cow::Owned(String::from("FOO")) as Cow);
}

Клонирует данных, если они еще не принадлежат ему.


use std::borrow::Cow;
fn main(){
    let s = "Hello world!";
    let cow = Cow::Borrowed(s);
    assert_eq!(cow.into_owned(), String::from(s));
}

Более быстрая и компактная реализация Cow.

crate beef

Есть две версии Cow:

  • beef::Cow - состоит из трех слов: указатель, длина и емкость. Он хранит тег владения в емкости.
  • beef::lean::Cow - имеет ширину 2 слова и сохраняет длину, емкость и тег владельца в одном слове.
use beef::Cow;
fn main(){
    let borrowed: Cow<str> = Cow::borrowed("Hello");
    let owned: Cow<str> = Cow::owned(String::from("World"));

    assert_eq!(
        format!("{} {}!", borrowed, owned),
        "Hello World!",
    );
}

Этот фрагмент демонстрирует использование типа Cow (Clone-on-write) для оптимизации работы со строками: программа выделяет память под новую строку только в том случае, если в исходном тексте действительно есть заглавные буквы.

#![allow(unused)]
fn main() {
use std::borrow::Cow;

fn to_lowercase_if_needed(text: &str) -> Cow<'_,str> {
    if !text.chars().any(char::is_uppercase) {
        Cow::Borrowed(text)
    } else {
        Cow::Owned(text.to_lowercase())
    }
}

#[test]
fn test_to_lowercase_if_needed_variant_selection() {
    assert!(matches!(
        to_lowercase_if_needed("привет!"),
        Cow::Borrowed(_)
    ));
    assert!(matches!(
        to_lowercase_if_needed("ПрИвЕт!"),
        Cow::Owned(_)
    ));
}
}

При преобразовании Path в string некоторые UTF-8 не могут быть преобразованы, если надо заменить символы на верные то вернется новая строка Cow::Owned иначе неизменная ссылающаяся на оригинал Cow::Borrowed


use std::borrow::Cow;
use std::path::Path;
fn main(){
    let path = Path::new("foo.txt");
    match path.to_string_lossy() { 
           Cow::Borrowed(_str_ref) => println!("path was valid UTF-8"),
           Cow::Owned(_new_string) => println!("path was not valid UTF-8"),
    }
}
use std::borrow::Cow;
fn describe(error: &Error) -> Cow<'static, str> {
    match *error {
        // Возврат &'str - заимствованная ссылка на static str.
        Error::NotFound => "Error: Not found".into(),
        
        // Возврат String -  строка, выделенная в куче.
        Error::Custom(e) => format!("Error: {}", e).into(),
    }
}

1. Функция, редко изменяющая данные

(это применение определяется не во время компиляции, а во время выполнения)


use std::borrow::Cow;

// Что, если в 99,9% вызовов строка не содержит пробелов?
// В таких случаях мы могли бы избежать вызова to_string() и создания ненужной копии строки. 
// Однако, если мы хотим реализовать такую ​​логику, мы не можем использовать ни String ни &str
fn remove_whitespaces(s: &str) -> String {
    s.to_string().replace(' ', "")
}

fn cow_remove_whitespaces(s: &str) -> Cow {
    if s.contains(' ') {
        Cow::Owned(s.to_string().replace(' ', ""))
    } else {
        Cow::Borrowed(s)
    }
}
fn main() {
    let value:String = remove_whitespaces("Hello world!");
    println!("{}", value);

    let value: Cow  = cow_remove_whitespaces("Hello world!");
    println!("{}", &value);
    let for_string: String = value.into_owned();
}

2. Структура, необязательно владеющая данными

(это применение определяется не во время компиляции, а во время выполнения)


struct User<'a> {
    first_name: Cow<'a, str>,
    last_name: Cow<'a, str>,
}
impl<'a> User<'a> {
    pub fn new_owned(first_name: String, last_name: String) -> User<'static> {
        User {
            first_name: Cow::Owned(first_name),
            last_name: Cow::Owned(last_name),
        }
    }
    pub fn new_borrowed(first_name: &'a str, last_name: &'a str) -> Self {
        Self {
            first_name: Cow::Borrowed(first_name),
            last_name: Cow::Borrowed(last_name),
        }
    }

    // универсальный метод
    pub fn new< S >(first_name: S, last_name: S) -> User<'a> where S: Into> {
        User { 
            first_name: first_name.into(),
            last_name: last_name.into()
         }
    }

    pub fn first_name(&self) -> &str {
        &self.first_name
    }
    pub fn last_name(&self) -> &str {
        &self.last_name
    }
}
struct User2{
    first_name: Cow<'static, str>,
    last_name: Cow<'static, str>,
}
impl User2 {
    pub fn new< S >(first_name: S, last_name: S) -> User2 where S: Into> {
        User2 { 
            first_name: first_name.into(),
            last_name: last_name.into()
         }
    }

    pub fn first_name(&self) -> &str {
        &self.first_name
    }
    pub fn last_name(&self) -> &str {
        &self.last_name
    }
}
// Для serde надо явно указать borrow поля
#[derive(Debug, serde::Deserialize)]
struct User3{
    #[serde(borrow)]
    first_name: Cow<'static, str>,
    #[serde(borrow)]
    last_name: Cow<'static, str>,
}
fn main(){
// Статическое время жизни, поскольку оно владеет данными
    //let user: User<'static> = User::new_owned("James".to_owned(), "Bond".to_owned());
    let user: User<'static> = User::new("James".to_owned(), "Bond".to_owned());
    println!("Name: {} {}", user.first_name, user.last_name);

// Статическое время жизни, поскольку оно заимствует статические данные
    //let user: User<'static> = User::new_borrowed("Felix", "Leiter");
    let user: User<'static> = User::new("Felix", "Leiter");
    println!("Name: {} {}", user.first_name, user.last_name);

    let first_name: String = "Eve".to_owned();
    let last_name: String = "Moneypenny".to_owned();

// Нестатическое время жизни, поскольку оно заимствует данные
    let user: User = User::new_borrowed(&first_name, &last_name);
    println!("Name: {} {}", user.first_name, user.last_name);

    let user: User2 = User2::new("James".to_owned(), "Bond".to_owned());
    println!("Name: {} {}", user.first_name, user.last_name);
    let user: User2 = User2::new("Felix", "Leiter");
    println!("Name: {} {}", user.first_name, user.last_name);
}

3. Клон структуры записи

to_mut Если объект Cow принадлежит владельцу, он просто возвращает указатель на базовые данные, однако если он заимствован, данные сначала клонируются в принадлежащий ему объект.

Он позволяет реализовать интерфейс на основе структур, лениво сохраняя ссылки на данные и клонируя их только в том случае (и впервые) требуется мутация.


struct LazyBuffer<'a> {
    data: Cow<'a, [u8]>,
}
impl<'a> LazyBuffer<'a> {
    pub fn new(data: &'a[u8]) -> Self {
        Self {
            data: Cow::Borrowed(data),
        }
    }
    pub fn data(&self) -> &[u8] {
        &self.data
    }
    pub fn append(&mut self, data: &[u8]) {
        self.data.to_mut().extend(data)
    }
}
fn main(){
    let data = vec![0u8; 10];

    // Память еще не скопирована
    let mut buffer = LazyBuffer::new(&data);
    println!("{:?}", buffer.data());

    // Данные клонированы
    buffer.append(&[1, 2, 3]);
    println!("{:?}", buffer.data());

    // Данные не клонируются при дальнейших попытках
    buffer.append(&[4, 5, 6]);
    println!("{:?}", buffer.data());
}
/// Ошибка, возникающая, когда операция не разрешена текущим состоянием объекта.
#[cfg_attr(target_family = "wasm", wasm_bindgen)]
#[derive(Debug)]
pub struct StateError {
    /// Message describing the problem.
    message: Cow<'static, str>,

    /// Stacktrace of this [`StateError`].
    trace: Trace,
}
impl StateError {
    #[must_use]
    pub fn new<T: Into<Cow<'static, str>>>(message: T, trace: Trace) -> Self {
        Self {
            message: message.into(),
            trace,
        }
    }
}

Применение

Храним строки за ссылками но при необходимости их изменить будем им выделять новую память


struct Word {
    data:Cow<'static,str>
}
impl Word {
   fn new(s:&'static str)->Self {
        Self{data:Cow::Borrowed(s)} // или Self{data:s.into()}
    }
    fn more_than_three(&mut self){
        if self.data.len() < 4 { 
            self.data.to_mut().push_str("!!!"); // тут происходит alloc и можем push_str из-за Cow impl Deref
        }
    }
}
fn main() {
    let mut words:Vec = vec![];
    let mut iter = "one two three".split_whitespace();
    while let  Some(s) = iter.next(){
        words.push(Word::new(s));
    } 

    for w in words.iter_mut(){
        w.more_than_three(); // изменение некоторых данных
    }
    assert_eq!(words[0].data,"one!!!");
    assert_eq!(words[1].data,"two!!!");
    assert_eq!(words[2].data,"three");
}

Применение

храним данными за ссылками т.е. &str вместо String и &[] вместо Vec

Что бы не выделять память под String если нет нужды, а вернуть можем обратно эти же данные, но в Cow

Поскольку тип Cow часто используется для строк, в стандартной библиотеке имеется специальная поддержка для Cow<'a, str>. Этот тип позволяет выполнять преобразования в типы String и &str и обратно.

use std::borrow::Cow;
fn get_name() -> Cow<'static, str> {
         std::env::var("USER")
                  .map(|v| Cow::Owned(v)) // или  .map(|v| v.into())
                  .unwrap_or(Cow::Borrowed("кем бы ты ни был")) // или .unwrap_or("кем бы ты ни был".into())
}

Если s и так в нижнем регистре — экономим аллокацию. Если строка уже в lowercase, то так и оставить иначе отдать String

use std::borrow::Cow;
fn to_lowercase<'a>(s: &'a str) -> Cow<'a, str> {
    if s.chars().all(char::is_lowercase) {
         Cow::Borrowed(s)
    } else {
         Cow::Owned(s.to_lowercase())
    }
} 

Минусы: тоскать ifetime параметр

Теперь токен может быть прозрачно создан как из &str, так и из String.

Связанное с токеном время жизни больше не проблема для данных, созданных на стеке.

Можно даже пересылать токен между потоками


struct Token<'a> {
        raw: Cow<'a, str>
}

impl<'a> Token<'a> {
        pub fn new< S >(raw: S) -> Token<'a> where S: Into> {
            Token { raw: raw.into() }
        }
}
fn main(){
    let token_static = Token::new("abc123");
    println!("six:{}",token_static.raw);

    let secret: String =  String::from("abc123") ;
    let token_owned = Token::new(secret);
    println!("six:{}",token_owned.raw);
}

Применение в std

struct.String.html#method.from_utf8_lossy

fn from_utf8_lossy(v: &[u8]) -> Cow<'_, str> эта функция возвращает Cow<'a, str>.

Если наш фрагмент байтов недействителен в формате UTF-8, нам нужно вставить символы замены, которые изменят размер строки и, следовательно, потребуют расширения String. Но если UTF-8 уже действителен, нам не нужно новое выделение.


fn main(){
    let sparkle_heart = vec![240, 159, 146, 150];
    let sparkle_heart:Cow<'_, str>  = String::from_utf8_lossy(&sparkle_heart);
}

Применение

ironrdp-cliprdr

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FileContentsResponse<'a> {
    is_error: bool,
    stream_id: u32,
    data: Cow<'a, [u8]>,
}
impl<'a> FileContentsResponse<'a> {
    pub fn new_data_response(stream_id: u32, data: impl Into<Cow<'a, [u8]>>) -> Self {
        Self {
            is_error: false,
            stream_id,
            data: data.into(),
        }
    }
    /// Creates a new `FileContentsResponse` with u64 size value
    pub fn new_size_response(stream_id: u32, size: u64) -> Self {
        Self {
            is_error: false,
            stream_id,
            data: Cow::Owned(size.to_le_bytes().to_vec()),
        }
    }
    /// Creates new `FileContentsResponse` with error
    pub fn new_error(stream_id: u32) -> Self {
        Self {
            is_error: true,
            stream_id,
            data: Cow::Borrowed(&[]),
        }
    }
}

можно ссылку передавать в add и можно владение


struct Store<'l>{
    data:Vec>
}
impl<'b> Store<'b>{
    fn new()->Self{
        Self{data:vec![]}
    }
    fn add(&mut self,s:Cow<'b,Value>){
        self.data.push(s);
    }
}

#[derive(Debug,Clone)]
struct Value(String);

use std::convert::From;
impl<'a> From<&'a Value> for Cow<'a, Value> {
    fn from(a: &'a Value) -> Self {
        Cow::Borrowed(a)
    }
}
fn main() {
    let v = Value(String::from("hello"));
    let mut store = Store::new();
    store.add(Cow::Borrowed(&v));
    store.add((&v).into());
    {
        let v = Value(String::from("hello2"));
        store.add(Cow::Owned(v));
    }
    println!("{:?}",store.data);
}

#[serde(borrow)]

Отличным примером того, как вы можете использовать суперспособности Cow в своих собственных структурах, ссылаясь на входные данные, а не на их копирование, является атрибут #[serde(borrow)]

Serde будет по умолчанию заполнить эту bar Cow принадлежащей String

#[derive(Debug, Deserialize)]
struct Foo<'input> {
    bar: Cow<'input, str>,
}

Однако, если вы пишете это как

#[derive(Debug, Deserialize)]
struct Foo<'input> {
    #[serde(borrow)]
    bar: Cow<'input, str>,
}

Serde попытается создать заимствованную версию Cow::Borrowed

Это будет работать, однако, когда входную строку не нужно настраивать.

У Cow же есть Deref

from-str-to-cow

Это правильно: Клонировать для изменения, а не копировать для чтения.

Это потому, что в Rust, Copy trait гарантированно является простой memcpy операцией, а Clone также может выполнять пользовательскую логику (например, рекурсивно клонировать HashMap<String, String>

Благодаря реализации Deref признака вы можете использовать ссылку на Cow<'static, str> вместо &str. Это означает, что Cow<'static, str> можно увидеть ссылку на строку без необходимости ее преобразования.

pub struct MySQL {
        pub host: Cow<'static, str>,   // вместо  pub host:&a str
}
impl Default for MySQL {
    fn default() -> Self {
       MySQL {host: "127.0.0.1".into() } ✅
    }
}

У Cow есть Deref. Достаточно просто писать "127.0.0.1" вместо Cow::Borrowed.

impl Default for MySQL {
    fn default() -> Self {
        MySQL {host: Cow::Borrowed("127.0.0.1")} ❌
    }
}