Главная страница
Навигация по странице:

  • Листинг 8-1: Создание нового пустого вектора для хранения значений типа

  • Листинг 8-2: Создание нового вектора, содержащего значения

  • Изменение вектора Чтобы создать вектор и затем добавить к нему элементы, можно использовать метод push показанный в листинге 8-3.Листинг 8-3: Использование метода

  • для добавления значений в вектор

  • Листинг 8-4. Использование синтаксиса индексации и метода get для доступа к элементу в векторе

  • Листинг 8-5. Попытка доступа к элементу с индексом 100 в векторе, содержащем пять элементов

  • Листинг 8-6. Попытка добавить некоторый элемент в вектор, в то время когда есть ссылка на элемент вектора

  • Перебор значений в векторе

  • Листинг 8-7. Печать каждого элемента векторе, при помощи итерирования по элементам вектора с помощью цикла

  • Листинг 8-8. Итерирование и изменение элементов вектора по изменяемым ссылкам

  • Использование перечислений для хранения множества разных типов

  • Листинг 8-9: Определение enum для хранения значений разных типов в одном векторе

  • Удаление элементов из вектора

  • Сохранение текста с UTF-8 кодировкой в строках

  • Листинг 8-11. Создание новой пустой String строки

  • Листинг 8-12: Использование метода to_string для создания экземпляра типа String из строкового литерала

  • Листинг 8-13: Использование функции String::from для создания экземпляра типа String из строкового литерала

  • Листинг 8-14: Хранение приветствий в строках на разных языках

  • Обновление строковых данных

  • Язык программирования Rust


    Скачать 7.02 Mb.
    НазваниеЯзык программирования Rust
    Дата12.04.2023
    Размер7.02 Mb.
    Формат файлаpdf
    Имя файлаThe Rust Programming Language_ru.pdf
    ТипУчебник
    #1056301
    страница17 из 62
    1   ...   13   14   15   16   17   18   19   20   ...   62
    Создание нового вектора
    Чтобы создать новый пустой вектор, мы вызываем функцию
    Vec::new
    , как показано в листинге 8-1.
    Листинг 8-1: Создание нового пустого вектора для хранения значений типа
    i32
    Обратите внимание, что здесь мы добавили аннотацию типа. Поскольку мы не вставляем никаких значений в этот вектор, Rust не знает, какие элементы мы собираемся хранить.
    Это важный момент. Векторы реализованы с использованием обобщённых типов; мы рассмотрим, как использовать обобщённые типы с вашими собственными типами в
    Главе 10. А пока знайте, что тип
    Vec
    , предоставляемый стандартной библиотекой,
    может хранить любой тип. Когда мы создаём новый вектор для хранения конкретного типа, мы можем указать этот тип в угловых скобках. В листинге 8-1 мы сообщили Rust, что
    Vec
    в v
    будет хранить элементы типа i32
    Чаще всего вы будете создавать
    Vec
    с начальными значениями и Rust может определить тип сохраняемых вами значений, но иногда вам всё же придётся указывать аннотацию типа. Для удобства Rust предоставляет макрос vec!
    , который создаст новый вектор, содержащий заданные вами значения. В листинге 8-2 создаётся новый
    Vec
    ,
    который будет хранить значения
    1
    ,
    2
    и
    3
    . Целочисленный тип — i32, потому что это целочисленный тип по умолчанию, как мы обсуждали в разделе «Типы данных» главы 3.
    Числовым типом является i32
    , потому что это тип по умолчанию для целочисленных значений, о чём упоминалось в разделе
    “Типы данных”
    Главы 3.
    Листинг 8-2: Создание нового вектора, содержащего значения
    Поскольку мы указали начальные значения типа i32
    , Rust может сделать вывод, что тип переменной v
    это
    Vec
    и аннотация типа здесь не нужна. Далее мы посмотрим как изменять вектор.
    let v:
    Vec
    <
    i32
    > =
    Vec
    ::new(); let v = vec!
    [
    1
    ,
    2
    ,
    3
    ];

    Изменение вектора
    Чтобы создать вектор и затем добавить к нему элементы, можно использовать метод push показанный в листинге 8-3.
    Листинг 8-3: Использование метода
    push
    для добавления значений в вектор
    Как и с любой переменной, если мы хотим изменить её значение, нам нужно сделать её
    изменяемой с помощью ключевого слова mut
    , что обсуждалось в Главе 3. Все числа которые мы помещаем в вектор имеют тип i32
    по этому Rust с лёгкостью выводит тип вектора, по этой причине нам не нужна здесь аннотация типа вектора
    Vec
    Чтение данных вектора
    Есть два способа сослаться на значение, хранящееся в векторе: с помощью индекса или метода get
    . В следующих примерах для большей ясности мы указали типы значений,
    возвращаемых этими функциями.
    В листинге 8-4 показаны оба метода доступа к значению в векторе: либо с помощью синтаксиса индексации и с помощью метода get
    Листинг 8-4. Использование синтаксиса индексации и метода
    get
    для доступа к элементу в векторе
    Обратите внимание здесь на пару деталей. Мы используем значение индекса
    2
    для получения третьего элемента: векторы индексируются начиная с нуля. Указывая
    &
    и
    []
    мы получаем ссылку на элемент по указанному индексу. Когда мы используем метод get содержащего индекс, переданный в качестве аргумента, мы получаем тип
    Option<&T>
    ,
    который мы можем проверить с помощью match let mut v =
    Vec
    ::new(); v.push(
    5
    ); v.push(
    6
    ); v.push(
    7
    ); v.push(
    8
    ); let v = vec!
    [
    1
    ,
    2
    ,
    3
    ,
    4
    ,
    5
    ]; let third: &
    i32
    = &v[
    2
    ]; println!
    (
    "The third element is {}"
    , third); let third:
    Option
    <&
    i32
    > = v.get(
    2
    ); match third {
    Some
    (third) => println!
    (
    "The third element is {}"
    , third),
    None
    => println!
    (
    "There is no third element."
    ),
    }

    Причина, по которой Rust предоставляет два способа ссылки на элемент, заключается в том, что вы можете выбрать, как программа будет себя вести, когда вы попытаетесь использовать значение индекса за пределами диапазона существующих элементов. В
    качестве примера давайте посмотрим, что происходит, когда у нас есть вектор из пяти элементов, а затем мы пытаемся получить доступ к элементу с индексом 100 с помощью каждого метода, как показано в листинге 8-5.
    Листинг 8-5. Попытка доступа к элементу с индексом 100 в векторе, содержащем пять элементов
    Когда мы запускаем этот код, первая строка с
    &v[100]
    вызовет панику программы,
    потому что происходит попытка получить ссылку на несуществующий элемент. Такой подход лучше всего использовать, когда вы хотите, чтобы ваша программа аварийно завершила работу при попытке доступа к элементу за пределами вектора.
    Когда методу get передаётся индекс, который находится за пределами вектора, он без паники возвращает
    None
    . Вы могли бы использовать такой подход, если доступ к элементу за пределами диапазона вектора происходит время от времени при нормальных обстоятельствах. Тогда ваш код будет иметь логику для обработки наличия
    Some(&element)
    или
    None
    , как обсуждалось в Главе 6. Например, индекс может исходить от человека, вводящего число. Если пользователь случайно введёт слишком большое число, то программа получит значение
    None и у вас будет возможность сообщить пользователю, сколько элементов находится в текущем векторе, и дать ему возможность ввести допустимое значение. Такое поведение было бы более дружелюбным для пользователя, чем внезапный сбой программы из-за опечатки!
    Когда у программы есть действительная ссылка, borrow checker (средство проверки заимствований), обеспечивает соблюдение правил владения и заимствования
    (описанные в Главе 4), чтобы гарантировать, что эта ссылка и любые другие ссылки на содержимое вектора остаются действительными. Вспомните правило, которое гласит,
    что у вас не может быть изменяемых и неизменяемых ссылок в одной и той же области.
    Это правило применяется в листинге 8-6, где мы храним неизменяемую ссылку на первый элемент вектора и затем пытаемся добавить элемент в конец вектора. Данная программа не будет работать, если мы также попробуем сослаться на данный элемент позже в функции:
    let v = vec!
    [
    1
    ,
    2
    ,
    3
    ,
    4
    ,
    5
    ]; let does_not_exist = &v[
    100
    ]; let does_not_exist = v.get(
    100
    ); let mut v = vec!
    [
    1
    ,
    2
    ,
    3
    ,
    4
    ,
    5
    ]; let first = &v[
    0
    ]; v.push(
    6
    ); println!
    (
    "The first element is: {}"
    , first);

    Листинг 8-6. Попытка добавить некоторый элемент в вектор, в то время когда есть ссылка на элемент
    вектора
    Компиляция этого кода приведёт к ошибке:
    Код в листинге 8-6 может выглядеть так, как будто он должен работать. Почему ссылка на первый элемент должна заботиться об изменениях в конце вектора? Эта ошибка возникает из-за особенности того, как работают векторы: поскольку векторы размещают значения в памяти друг за другом, добавление нового элемента в конец вектора может потребовать выделения новой памяти и копирования старых элементов в новое пространство, если нет достаточного места, чтобы разместить все элементы друг за другом там, где в данный момент хранится вектор. В этом случае ссылка на первый элемент будет указывать на освобождённую память. Правила заимствования предотвращают попадание программ в такую ситуацию.
    Примечание: Дополнительные сведения о реализации типа
    Vec
    смотрите в разделе "The Rustonomicon"
    Перебор значений в векторе
    Для доступа к каждому элементу вектора по очереди, мы итерируем все элементы,
    вместо использования индексов для доступа к одному за раз. В листинге 8-7 показано,
    как использовать цикл for для получения неизменяемых ссылок на каждый элемент в векторе значений типа i32
    и их вывода.
    $
    cargo run
    Compiling collections v0.1.0 (file:///projects/collections) error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
    -->
    src/main.rs:6:5
    |
    4 | let first = &v[0];
    | - immutable borrow occurs here
    5 |
    6 | v.push(6);
    | ^^^^^^^^^ mutable borrow occurs here
    7 |
    8 | println!("The first element is: {}", first);
    | ----- immutable borrow later used here
    For more information about this error, try `rustc --explain E0502`. error: could not compile `collections` due to previous error

    Листинг 8-7. Печать каждого элемента векторе, при помощи итерирования по элементам вектора с
    помощью цикла
    for
    Мы также можем итерировать изменяемые ссылки на каждый элемент изменяемого вектора, чтобы вносить изменения во все элементы. Цикл for в листинге 8-8 добавит
    50
    к каждому элементу.
    Листинг 8-8. Итерирование и изменение элементов вектора по изменяемым ссылкам
    Чтобы изменить значение на которое ссылается изменяемая ссылка, мы должны использовать оператор разыменования ссылки
    *
    для получения значения по ссылке в переменной i
    прежде чем использовать оператор
    +=
    . Мы поговорим подробнее об операторе разыменования в разделе
    “Следование по указателю к значению с помощью оператора разыменования”
    Главы 15.
    Перебор вектора, будь то неизменяемый или изменяемый, безопасен из-за правил проверки заимствования. Если бы мы попытались вставить или удалить элементы в телах цикла for в листингах 8-7 и 8-8, мы бы получили ошибку компилятора, подобную той, которую мы получили с кодом в листинге 8-6. Ссылка на вектор, содержащийся в цикле for, предотвращает одновременную модификацию всего вектора.
    Использование перечислений для хранения множества разных типов
    Векторы могут хранить значения только одинакового типа. Это может быть неудобно;
    определённо могут быть случаи когда надо хранить список элементов разных типов. К
    счастью, варианты перечисления определены для одного и того же типа перечисления,
    поэтому, когда нам нужен один тип для представления элементов разных типов, мы можем определить и использовать перечисление!
    Например, мы хотим получить значения из строки в электронной таблице где некоторые столбцы строки содержат целые числа, некоторые числа с плавающей точкой, а другие - строковые значения. Можно определить перечисление, варианты которого будут содержать разные типы значений и тогда все варианты перечисления будут считаться одними и тем же типом: типом самого перечисления. Затем мы можем создать вектор для хранения этого перечисления и, в конечном счёте, для хранения различных типов.
    Мы покажем это в листинге 8-9.
    let v = vec!
    [
    100
    ,
    32
    ,
    57
    ]; for i in
    &v { println!
    (
    "{}"
    , i);
    } let mut v = vec!
    [
    100
    ,
    32
    ,
    57
    ]; for i in
    &
    mut v {
    *i +=
    50
    ;
    }

    Листинг 8-9: Определение
    enum
    для хранения значений разных типов в одном векторе
    Rust должен знать, какие типы будут в векторе во время компиляции, чтобы точно знать сколько памяти в куче потребуется для хранения каждого элемента. Мы также должны чётко указать, какие типы разрешены в этом векторе. Если бы Rust позволял вектору содержать любой тип, то был бы шанс что один или несколько типов вызовут ошибки при выполнении операций над элементами вектора. Использование перечисления вместе с выражением match означает, что во время компиляции Rust гарантирует, что все возможные случаи будут обработаны, как обсуждалось в главе 6.
    Если вы не знаете исчерпывающий набор типов, которые программа получит во время выполнения для хранения в векторе, то техника использования перечисления не сработает. Вместо этого вы можете использовать типаж-объект, который мы рассмотрим в главе 17.
    Теперь, когда мы обсудили некоторые из наиболее распространённых способов использования векторов, обязательно ознакомьтесь с документацией по API вектора для всего множества полезных методов, определённых в
    Vec
    стандартной библиотеки.
    Например, в дополнение к методу push
    , существует метод pop
    , который удаляет и возвращает последний элемент.
    Удаление элементов из вектора
    Подобно структурам struct
    , вектор высвобождает свою память когда выходит из области видимости, что показано в листинге 8-10.
    Листинг 8-10. Показано как удаляется вектор и его элементы
    Когда вектор удаляется, всё его содержимое также удаляется: удаление вектора означает и удаление значений, которые он содержит. Средство проверки заимствования enum
    SpreadsheetCell
    {
    Int(
    i32
    ),
    Float(
    f64
    ),
    Text(
    String
    ),
    } let row = vec!
    [
    SpreadsheetCell::Int(
    3
    ),
    SpreadsheetCell::Text(
    String
    ::from(
    "blue"
    )),
    SpreadsheetCell::Float(
    10.12
    ),
    ];
    { let v = vec!
    [
    1
    ,
    2
    ,
    3
    ,
    4
    ];
    // do stuff with v
    }
    // <- v goes out of scope and is freed here
    гарантирует, что любые ссылки на содержимое вектора используются только тогда, когда сам вектор действителен.
    Давайте перейдём к следующему типу коллекции:
    String
    !

    Сохранение текста с UTF-8 кодировкой в строках
    Мы говорили о строках в главе 4, но сейчас мы рассмотрим их более подробно. Новички в Rust обычно застревают на строках из-за комбинации трёх причин: склонность Rust компилятора к выявлению возможных ошибок, более сложная структура данных чем считают многие программисты и UTF-8. Эти факторы объединяются таким образом, что тема может показаться сложной, если вы пришли из других языков программирования.
    Полезно обсуждать строки в контексте коллекций, потому что строки реализованы в виде набора байтов, плюс некоторые методы для обеспечения полезной функциональности,
    когда эти байты интерпретируются как текст. В этом разделе мы поговорим об операциях над
    String таких как создание, обновление и чтение, которые есть у каждого типа коллекций. Мы также обсудим какими особенностями
    String отличается от других коллекций, а именно каким образом индексирование в
    String осложняется различием между тем как люди и компьютеры интерпретируют данные заключённые в
    String
    Что же такое строка?
    Сначала мы определим, что мы подразумеваем под термином строка (string). В Rust есть только один строковый тип в ядре языка - срез строки str
    , обычно используемый в заимствованном виде как
    &str
    . В Главе 4 мы говорили о срезах строк, string slices,
    которые являются ссылками на некоторые строковые данные в кодировке UTF-8.
    Например, строковые литералы хранятся в двоичном файле программы и поэтому являются срезами строк.
    Тип
    String предоставляемый стандартной библиотекой Rust, не встроен в ядро языка и является расширяемым, изменяемым, владеющим, строковым типом в UTF-8 кодировке.
    Когда Rustaceans говорят о "строках" то, они обычно имеют ввиду типы
    String или строковые срезы
    &str
    , а не просто один из них. Хотя этот раздел в основном посвящён
    String
    , оба типа интенсивно используются в стандартной библиотеке Rust, оба, и
    String и строковые срезы, кодируются в UTF-8.
    Создание новых строк
    Многие из тех же операций, которые доступны
    Vec
    , доступны также в
    String
    ,
    потому что
    String фактически реализован как обёртка вокруг вектора байтов с некоторыми дополнительными гарантиями, ограничениями и возможностями.
    Примером функции, которая одинаково работает с
    Vec
    и
    String
    , является функция new
    , создающая новый экземпляр типа, и показана в Листинге 8-11.
    Листинг 8-11. Создание новой пустой
    String
    строки
    let mut s =
    String
    ::new();

    Эта строка создаёт новую пустую строковую переменную с именем s
    , в которую мы можем затем загрузить данные. Часто у нас есть некоторые начальные данные, которые мы хотим назначить строке. Для этого мы используем метод to_string доступный для любого типа, который реализует типаж
    Display
    , как у строковых литералов. Листинг 8-12
    показывает два примера.
    Листинг 8-12: Использование метода
    to_string
    для создания экземпляра типа
    String
    из строкового
    литерала
    Эти выражения создают строку с initial contents
    Мы также можем использовать функцию
    String::from для создания
    String из строкового литерала. Код листинга 8-13 является эквивалентным коду из листинга 8-12,
    который использует функцию to_string
    :
    Листинг 8-13: Использование функции
    String::from
    для создания экземпляра типа
    String
    из строкового
    литерала
    Поскольку строки используются для очень многих вещей, можно использовать множество API для строк, предоставляющих множество возможностей. Некоторые из них могут показаться избыточными, но все они занимаются своим делом! В данном случае
    String::from и to_string делают одно и тоже, поэтому выбор зависит от стиля который вам больше импонирует.
    Запомните, что строки хранятся в кодировке UTF-8, поэтому можно использовать любые правильно кодированные данные в них, как показано в листинге 8-14:
    Листинг 8-14: Хранение приветствий в строках на разных языках
    let data =
    "initial contents"
    ; let s = data.to_string();
    // the method also works on a literal directly:
    let s =
    "initial contents"
    .to_string(); let s =
    String
    ::from(
    "initial contents"
    ); let hello =
    String
    ::from(
    "ﻢﻜﯿﻠﻋ مﻼﺴﻟا"
    ); let hello =
    String
    ::from(
    "Dobrý den"
    ); let hello =
    String
    ::from(
    "Hello"
    ); let hello =
    String
    ::from(
    "םוֹלָשׁ"
    ); let hello =
    String
    ::from(
    "नम े"
    ); let hello =
    String
    ::from(
    "こんにちは"
    ); let hello =
    String
    ::from(
    "안녕하세요"
    ); let hello =
    String
    ::from(
    "你好"
    ); let hello =
    String
    ::from(
    "Olá"
    ); let hello =
    String
    ::from(
    "Здравствуйте"
    ); let hello =
    String
    ::from(
    "Hola"
    );

    Все это допустимые
    String значения.
    Обновление строковых данных
    Строка
    String может увеличиваться в размере, а её содержимое может меняться, по аналогии как содержимое
    Vec
    при вставке в него большего количества данных.
    Кроме того, можно использовать оператор
    +
    или макрос format!
    для объединения значений
    String
    1   ...   13   14   15   16   17   18   19   20   ...   62


    написать администратору сайта