Rust сравнение строки

Обновлено: 05.07.2024

String сохраняется как вектор байт ( Vec<u8> ), но с гарантией, что это всегда будет действительная UTF-8 последовательность. String выделяется в куче, расширяемая и не заканчивается нулевым байтом (не null-terminated).

&str - это срез ( &[u8] ), который всегда указывает на действительную UTF-8 последовательность, и является отображением String , так же как и &[T] - отображение Vec<T> .

Больше методов str и String вы можете найти в описании модулей std::str и std::string.

Литералы и экранирование

Есть несколько способов написать строковый литерал со специальными символами в нём. Все способы приведут к одной и той же строке, так что лучше использовать тот способ, который легче всего написать. Аналогично все способы записать строковый литера из байтов в итоге дадут &[u8; N] .

Обычно специальные символы экранируются с помощью обратной косой черты: \ . В этом случае вы можете добавить в вашу строку любые символы, даже непечатаемые и те, которые вы не знаете как набрать. Если вы хотите добавить обратную косую черту, экранируйте его с помощью ещё одной: \\ .

Строковые или символьные разделители литералов (кавычки, встречающиеся внутри другого литерала, должны быть экранированы: "\"" , '.' .

Иногда приходится экранировать слишком много символов или легче записать строку как она есть. В этот момент в игру вступают сырые строковые литералы.

Хотите строку, которая не UTF-8? (Помните, str и String должны содержать действительные UTF-8 последовательности). Или возможно вы хотите массив байтов, которые в основном текст? Байтовые строки вас спасут!

Для преобразования между кодировками символов, посмотрите крейт encoding.

Более детальный список способов записи строковых литералов и экранирования символов можно найти в главе 'Tokens' Rust Reference.

Вопрос: После исполнения кода я получаю Some('H') . Как правильно получить одиночный символ строки?

Код:


2,807 3 3 золотых знака 24 24 серебряных знака 50 50 бронзовых знаков Код примера не совпадает с заголовком вопроса: вы пытаетесь итерировать символы в строке и достать некоторый по счету. Индексирование строк эффективно делается встроенными операторами индексирования по диапазону, но на выходе получаются отрезки строк и с индексами нужно быть осторожным, иначе можно "разрезать" кодовую последовательность UTF-8 и получить панику. См. также метод char_indices() . Я с вами согласен " Индексирование строк эффективно делается встроенными операторами индексирования по диапазону,". Он нашел самый ужасный метод получения индекса строки. По коду это целый цикл. fn nth(&mut self, mut n: usize) -> Option<Self::Item> < for x in self < if n == 0 < return Some(x) >n -= 1; > None > Бо это Iter и без цикла получить определенную позицию Iter нельзя.

"После компиляции и запуска получаю Some('H'). Как получить (просто индекс без посторонних символов) H без Some()?"

Я вообще худею от таких вопросов. Информация:

Option это аналог null , его безопасная версия. Тоесть если индекса 0 в строке не существует вернется None , а если индекс существует то там будет Some('') . Сам Option это enum имеющий или Some или None .

Как обрабатывать Option??

Плохой пример:

Лучше сами описывайте действия реакции на отсутствие индекса, чем используйте unwrap. Unwrap говорит если вернули Some(a) то запиши 'a' в переменную, а если индекса 0 не существует то прерви программу.

Совет: Не используйте метод NTH для таких целей как получить индекс строки. Сведи с тем что он работает для немного другой цели.

КДПВ


Этот пост посвящается всем тем, кого смущает необходимость использовать to_string() , чтобы заставить программы компилироваться. И я надеюсь пролить немного света на вопрос о том, почему в Rust два строковых типа String и &str .

Функции, которые принимают строки

Я хочу обсудить, как создавать интерфейсы, которые принимают строки. Я большой фанат гипермедии и увлечён созданием лёгких в использовании интерфейсов. Начнём с метода, который принимает String . Наш поиск приведёт нас к типу std::string::String , что для начала совсем не плохо.


Получаем ошибку компиляции:


Получается, что строковый литерал типа &str не совместим с типом String . Нам нужно поменять тип переменной message на String , чтобы компиляция удалась: let message = "привет, мир".to_string(); . Так заработает, но это всё равно что использовать clone() для починки ошибок владения-наследования. Вот три причины, чтобы поменять тип аргумента print_me на &str :

  • Символ & обозначает ссылочный тип, то есть мы даём переменную взаймы. Когда print_me заканчивает работу с переменной, право владения возвращается к её изначальному владельцу. Если у нас нет хорошей причины, для передачи владения переменной message в нашу функцию, нам следует использовать заимствование.
  • Использование ссылки более эффективно. Использование String для message означает, что программа должна скопировать значение. При использовании ссылки, такой как &str , копирования не происходит.
  • Тип String может волшебным образом превращаться в &str с использованием типажа Deref и приведения типов. Пример позволит понять этот момент намного лучше.

Пример приведения с разыменованием

В этом примере строки создаются четырьмя разными способами, и все они работают с функцией print_me . Основной момент, благодаря которому всё это работает, — передача значений по ссылке. Вместо того, чтобы передавать владеющую строку owned_string как String , мы передаём её как указатель &String . Когда компилятор видит, что &String передаётся в функцию, которая принимает &str , он приводит &String к &str . Точно такая же конверсия используется при использовании строк с обычным и атомарным счётчиком ссылок. Переменная string уже является ссылкой, поэтому нет необходимости использовать & при вызове print_me(string) . Обладая этим знанием, нам больше не нужно постоянно вызывать .to_string() по нашему коду.


Вы так же можете использовать приведение с разыменованием с другими типами, такими как вектор Vec . Всё таки String — это просто вектор восьмибайтных символов. Про приведение с разыменованием (англ.) можно подробнее почитать в книге «Язык программирования Rust» (англ.).

Использование структур

На данный момент мы должны уже быть свободны от лишних вызовов to_string() . Однако, у нас могут возникнуть некоторые проблемы при использовании структур. Используя имеющиеся знания, мы могли бы создать такую структуру:


Мы получим такую ошибку:


Rust пытается удостовериться, что Person не может пережить ссылку на name . Если Person переживёт name , то есть риск падения программы. Основная цель Rust — не допустить этого. Давайте заставим этот код компилироваться. Нам понадобится указать время жизни (англ.), или область видимости, так, чтобы Rust смог нам обеспечить безопасность. Обычно время жизни называют так: 'a . Я не знаю, откуда пошла такая традиция, но мы ей последуем.


При попытке скомпилировать получим такую ошибку:


Давайте поразмыслим. Мы знаем, что хотим как-то донести до компилятора Rust мысль, что структура Person не должна пережить поле name . Так что нам нужно объявить время жизни структуры Person . Недолгие поиски приводят нас к синтаксису для объявления времени жизни: <'a> .


Это компилируется! Обычно мы реализуем на структурах некоторые методы. Давайте добавим к нашему классу Person метод greet .


Теперь мы получим такую ошибку:


У нашей структуры Person есть параметр времени жизни, так что наша реализация должны тоже его иметь. Давайте объявим время жизни 'a в реализации Person вот так: impl Person<'a> < . Увы, теперь мы получим такую странную ошибку компиляции:


Чтобы нам объявить время жизни, нам нужно указать время жизни сразу после impl вот так: impl<'a> Person < . Компилируем снова, получаем ошибку:


Уже понятнее. Давайте добавим параметр времени жизни в описании структуры Person её в реализации вот так: impl<'a> Person<'a> < . Теперь программа cкомпилируется. Вот полный рабочий код:

String или &str в структурах

Теперь возникает вопрос: когда стоит использовать String , а когда &str в структурах? Другими словами, когда следует использовать ссылку на другой тип в структуре? Нам следует использовать ссылки, если наша структура не требует владения переменной. Смысл может быть немного размыт, так что я использую несколько правил, для ответа на этот вопрос.

  • Нужно ли использовать переменную вне структуры? Вот немного надуманный пример:

Здесь мне стоит использовать ссылку, так как мне нужно будет использовать переменную до помещения в структуру. Пример из реальной жизни — rustc_serialize. Структуре Encoder не нужно владеть переменной writer , которая реализует типаж std::fmt::Write , поэтому используется только заимствование. На самом деле String реализует Write . В этом примере при использовании функции encode переменная типа String передаётся в Encoder и затем возвращается обратно в encode .

  • Мой тип большой? Если тип большой, то передача по ссылке позволит сберечь память. Помните, передача по ссылке не приводит к копированию переменных. Представьте буфер типа String с большим количеством данных. Его копирование при каждой передаче в другую функцию может значительно замедлить программу.

По поводу 'static

Думаю, что стоит обратить внимание на ещё один момент. Мы можем использовать статическое время жизни 'static (как в первом примере), чтобы заставить наш пример скомпилироваться, но я бы не советовал так делать:


Статическое время жизни 'static валидно на протяжении всей жизни программы. Вы вряд ли захотите, чтобы Person или name жили так долго. (Например статические строковые литералы, вкомпилированные в саму программу, обладают типом &'static str , то есть живут на протяжении всей жизни программы — прим. перев.)

Предыдущую статью восприняли лучше, чем я ожидал, так что решился на продолжение эксперимента. Это своеобразный ответ на перевод статьи Programming in D for C Programmers за авторством Дмитрия aka vintage. Как мне кажется, в области применения C Rust более уместен, чем замена Go, как предполагалось в прошлой статье. Тем интереснее будет сравнить. Опять таки, код на С приводить не буду, тем более что аналог на D всё равно смотрится лаконичнее.

Получаем размер типа в байтах

Напомню, что в С (и в С++) для этой цели существует специальный оператор sizeof , который может применяться как к типам, так и к переменным. В D же размер доступен через свойство (которое тоже можно применять и к переменным):

В Rust используется функция, которая обращается к внутренностям компилятора (соответствующему intrinsic):

При этом, по причине отсутствия перегрузки функций, для получения размера переменных используется другая функция — size_of_val . Возможно, такое разделение несколько менее удобно, зато не приходится вводить специальные ключевые слова — используются обычные языковые механизмы:

Забавный нюанс: в Rust пустые структуры (такие как Foo из примера) занимают 0 байт, соответственно массив любого размера таких структур тоже будет занимать 0 байт.
[Поиграться с кодом]

Получаем максимальное и минимальное значение типа

В D, опять-таки, используются свойства типов:

В Rust используются С-подобные константы:

Таблица соответствия типов

Сравнение не совсем правильное, так как в С используются платформозависимые типы, а в D наоборот — фиксированного размера. Для Rust подбирал именно аналоги фиксированного размера.

Особые значения чисел с плавающей точкой

Как видим, в Rust снова используются константы, которые, кстати, принято писать в верхнем регистре.

Остаток от деления вещественных чисел

Тут никаких откровений — в Rust, как и в D, имеется оператор %.

Обработка NaN значений

И в D, и в Rust сравнение с NaN даст в результате false .

Асерты — полезный механизм выявления ошибок

Оба языка предоставляют асерты "из коробки", но в D они являются специальной языковой конструкцией:

A в Rust — просто макросами:

Впрочем, есть и интересное отличие: в D асерты в релизной сборке отключаются, кроме специального случая assert(0) , который используется для обозначения недостижимого при нормальном выполнении кода.

В Rust они остаются и в релизе, впрочем, аналогичное поведение можно получить при помощи макроса debug_assert! . Для более явного обозначения недостижимого когда используется отдельный макрос unreachable! .

Итерирование по массиву (коллекции)

Особой разницы нет, хотя цикл for в Rust и не похож на своего родственника из С.

Инициализация элементов массива

В D мы можем инициализировать массив одним значением, как показано выше. Стоит заметить, что после создания массив сначала будет инициализирован значением по умолчанию того типа, который в нём содержится.

В Rust присутствует специальный синтаксис для этого случая.

Создание массивов переменной длины

D имеет встроенную поддержку массивов переменной длины:

Rust, следуя своей "философии явности", требует задать значение, которым будут инициализированы новые элементы при вызове метода resize . Поэтому правильнее пример будет записать следующим образом:

Обратите внимание, что нам не приходится указывать тип элементов содержащихся в векторе — они будут выведены автоматически.

Соединение строк

В D есть специальные перегружаемые операторы

=, предназначенные для соединения списков:

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

В Rust, с одной стороны, эти проблемы невозможны из-за необходимости явного приведения типов. С другой стороны, оператор += для строк всё-таки не реализован.

Форматированный вывод

Как видим, языки в этом плане не особо различаются. Разве что в Rust форматирование не похоже на "привычное" из С.

Обращение к функциям до объявления

Оба языка используют модули, поэтому порядок определения не имеет значения и предварительные объявления не нужны.

Функции без аргументов

Сравнение несколько теряет смысл в отрыве от С, так как оба языка не требуют указывать void для обозначения отсутствия аргументов.

Выход из нескольких блоков кода

Синтаксис break/continue с меткой практически идентичен.

Пространство имён структур

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

Ветвление по строковым значениям (например, обработка аргументов командной строки)

В данном случае особой разницы не видно, но в Rust конструкция match — это полноценное сравнение с образцом, что позволяет делать более хитрые вещи:

Выравнивание полей структур

В D есть специальный синтаксис, с помощью которого вы можете детально настроить выравнивание отдельных полей:

В Rust можно только полностью отключить выравнивание для отдельных структур:

Анонимные структуры и объединения

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

В Rust нет анонимных структур или объединений, поэтому аналогичный код будет выглядеть вот так:

Более того, Rust не позволит случайно обратиться не к тому полю объединения, которые было инициализировано. Поэтому и обращаться к ним придётся иначе:

Таким образом, объединения нельзя использовать как (полу)легальное преобразование типов, зато исключаются потенциальные ошибки.

Определение структур и переменных

Оба языка требуют раздельного объявления типа и переменной, то есть, как на С, записать не получится:

Получение смещения поля структуры

В D у полей есть специальное свойство offsetof :

На данный момент Rust не поддерживает такую возможность, так что при необходимости вам придётся вручную вычислять смещения, манипулируя указателями на члены структуры. Впрочем, offsetof является зарезервированным ключевым словом, а значит со временем такая функциональность должна появиться.

Инициализация объединений

D требует явного указания на то, какому полю объединения присваивается значение:

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

Инициализация структур

В D структуры можно инициализировать как по порядку, так и с указанием имён полей:

В Rust указание имён обязательно:

Инициализация массивов

В D существует много способов инициализации массива, в том числе с указанием индексов инициализируемых элементов:

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

Экранирование спецсимволов в строках

Оба языка, наряду с экранированием отдельных символов, поддерживают так называемые "сырые строки":

ASCII против многобайтных кодировок

В D поддерживается несколько видов строк, которые хранят символы разного типа:

В Rust существует только один тип строк, которые представляют последовательность UTF-8 байт:

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

Отображение перечисления на массив

Аналог на Rust с применением макроса collect! будет выглядеть следующим образом:

Создание новых типов

D позволяет создавать новые типы из имеющихся (strong typedef):

В том числе, с заданием дефолтного значения:

В Rust это делается через использование структуры-кортежа (tuple struct, перевод):

Создать значение без инициализации Rust и так не позволит, а для создания значения по умолчанию правильным будет реализовать трейт Default:

Сравнение структур

Сравнение строк

В обоих языках строки можно сравнивать на равенство и больше/меньше.

Сортировка массивов

D использует обобщённые реализации алгоритмов:

В Rust используется несколько другой подход: сортировка, как и некоторые другие алгоритмы, реализована для "срезов" (slice), а те контейнеры, для которых это имеет смысл, умеют к ним приводиться.

[Запустить]
Из мелких отличий: сравнение должно возвращать не bool , а Ordering (больше/меньше/равно).

Данное сравнение заставило задуматься, почему в Rust сделано не так как в D или С++. Навскидку не вижу преимуществ и недостатков обоих подходов, так что спишем просто на особенности языка.

Строковые литералы

Оба языка поддерживают многострочные строковые константы.

Обход структур данных

Несмотря на название, в этом пункте, по моему, демонстрируется только возможность вложенных функций обращаться к переменным объявленным в внешних, так что я взял на себя смелость переписать код:

В Rust можно объявлять вложенные функции, но захватывать переменные они не могут, для этого используются замыкания:

Динамические замыкания

В Rust тоже имеются лябмды/делегаты/замыкания. Пример был выше по тексту, ну а если вам интересны подробности, то загляните в документацию (перевод).

Переменное число аргументов

В D есть специальная конструкция ". " позволяющая принять несколько параметров в качестве одного типизированного массива:

Rust не имеет прямой поддержки переменного количества аргументов, вместо этого предлагается использовать срезы или итераторы:

Заключение

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

Читайте также: