Comments 18
Перейдя в директорию notes_list
, можно убеться, что созданный нами шаблонный проект собирается и запускается:
То есть, когда я указываю &mut для аргумента, его тип меняется? Был &Vec<String> - указатель на read-only список, а будет указатель на модифицируемый список &mut Vec<String>?
И в первом случае методы по добавлению будут недоступны?
Да, все верно
error[E0596]: cannot borrow `*notes` as mutable, as it is behind a `&` reference
--> src/main.rs:8:5
|
7 | fn add_note(notes: &Vec<String>) {
| ------------ help: consider changing this to be a mutable reference: `&mut Vec<String>`
8 | notes.push("test".to_string());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `notes` is a `&` reference, so the data it refers to cannot be borrowed as mutable
Есть ли смысл вместо ссылки на вектор использовать ссылку на слайс?
fn show_notes(notes: &[String]) {
}
// или даже так
fn show_notes(notes: &[&str]) {
}
Второе - не всегда, т.к., чтобы получить &[&str]
, нужно иметь что-то типа Vec<&str>
, его нельзя сделать напрямую из Vec<String>
. Первое - да, всегда лучше, чем &Vec<String>
, поскольку с точки зрения API эти два типа отличаются только возможностью узнать capacity вектора, но при этом при использовании среза у нас на один уровень указателей меньше.
Как верно написал , просто так рассматривать &Vec<String>
как &[&str]
не получится. Причина в том, что слайс &[T]
подразумевает последовательно идущие данные типа T
(с учётом выравниания), в то время как у String
представление включает в себя &str
, но не только его, из-за чего сугубо такой варинт без копирования (в новую структуру с новым представлением) не реализуем.
Но есть гораздо более гибкий вариант сделать метод более универсальным без необходимости в лишних копированиях, к которому мы придём в несколько шагов улучшения изначального метода:
пусть вместо строго строки (а, фактически, нас волнует даже не
String
, а то, что мы получаем ссылку ан еёstr
-составляющую) будет абстрактное нечто, что можно преобразовать в&str
:
fn show_notes<T: AsRef<str>>(notes: &Vec<T>) {
// Выводим пустую строку.
println!();
// Для каждой заметки в заметках ...
for note in notes {
// выводим её на экран.
println!("{}", note.as_ref())
}
}
Я метода появился типовой параметр T
, который должен быть чем-то, что может быть представлено как ссылка на str
.
на место вектора действительно можно поставить просто слайс (фактически, для вектора
Vec<T>
верно, что онAsRef[T]
, но тут в этом нет необходимости):
fn show_notes<T: AsRef<str>>(notes: &[T]) {
// Выводим пустую строку.
println!();
// Для каждой заметки в заметках ...
for note in notes {
// выводим её на экран.
println!("{}", note.as_ref())
}
}
наконец, вво имя красоты и компактности, воспользуемся синтаксическим сахаром, позволяющим описать п.1 более компактно:
fn show_notes(notes: &[impl AsRef<str>]) {
// Выводим пустую строку.
println!();
// Для каждой заметки в заметках ...
for note in notes {
// выводим её на экран.
println!("{}", note.as_ref())
}
}
По итогу, имеем метод, который принимает слайс, содержащий что-то, что можно представить как ссылку на str
, работающий как со String
, так со str
, так и с произвольными типами, реализующими AsRef<str>
(при этом лежащими в любом контейнере, который представим как слайс):
fn main() {
let notes = vec!["foo".to_string(), "bar".to_string()];
show_notes(¬es);
let notes = vec!["baz", "qux"];
show_notes(¬es);
let notes = vec!["dora", "prgrm"];
show_notes(¬es);
}
В качестве более продвинутого варианта, можно принимать даже не слайс (потому что непосредственной необходимости в последовательности данных у нас нет), а нечто, что можно последовательно обходить (итерироваться по этому), или даже то, почему можно устроить обход:
fn show_notes(notes: impl IntoIterator<Item = impl AsRef<str>>) {
// Выводим пустую строку.
println!();
// Для каждой заметки в заметках ...
for note in notes {
// выводим её на экран.
println!("{}", note.as_ref())
}
}
за счёт чего будет ещё большая гибкость:
let mut notes = HashMap::new();
notes.insert("a", "x");
notes.insert("b", "y");
show_notes(notes.keys());
show_notes(notes.values());
Разумеется, это не следует из этой статьи, поскольку в ней трейты не рассматривались, но, с точки зрения общего развития (и заманивания в секту Раста :) ) считаю это достаточно интересным.
Если интересен вопрос производительности, то, де факто, компилятор выполнит мономорфизацию, а именно создаст реализацию метода под каждый использующийся с ним тип (не каждый потенциально доступный, а именно каждый, который реально используется в программе), то есть, фактически, для оригинального варианта там будет всё тот же &Vec<String>
, для других &[String]
, &Vec<&str>
и так далее.
@PROgrammer_JARvis@Cerberuserогромное спасибо за ответы, познавательно!
std::io::stdin().read_line(&mut buffer).unwrap();
Что здесь обозначает вызов read_line
я понял, и наверное поймут большинство знающих какой либо другой язык, объяснять не надо. А вот что такое unwrap()
и зачем он тут нужен, совершенно не ясно, и в подробном разборе стоило бы упомянуть, а разъяснений не вижу.
Очень понравился подобный формат статьи, будучи совсем новичком в Расте обнаружил для себя кучу полезного.
Вот если бы еще тему GUI затронуть, и немного ООП на простых, но реальных примерах, да чтоб еще и с зачатками каких либо паттернов проектирования - счастью не было бы предела.
Да, я планировал, если статья окажется полезной, продолжить в том же духе. Возможно, развивая далее приложение для заметок.
В таких статьях мне всегда не хватает раздела который бы рассказывал как на это все написать тесты
Подробный разбор простого приложения на Rust