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') . Как правильно получить одиночный символ строки?
Код:
"После компиляции и запуска получаю 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 не имеет прямой поддержки переменного количества аргументов, вместо этого предлагается использовать срезы или итераторы:
Заключение
Вот и всё. Конечно, сравнение двух языков, отталкивающееся от особенностей третьего, получается довольно специфическим, но определённые выводы сделать можно. Предлагаю вам сделать их самостоятельно.
Читайте также: