Обновление: Часть 2 теперь доступна

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

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

Содержание предполагает наличие у читателя некоторых существующих знаний в области программирования, но может быть совершенно новым для Rust. Результатом каждой части серии станет функциональное и полезное приложение, которое будет использоваться в следующих статьях.

Я настоятельно рекомендую напечатать код, который вы видите, а не просто копировать и вставлять весь блок. Это один из самых эффективных способов получить лучшее понимание.

Начало работы

Установите rust с помощью приведенной ниже команды, которую можно найти на странице Начало работы.

Установить Rust

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Создать новый проект Rust

cargo new notes

cargo — менеджер пакетов Rust. Он поддерживает несколько команд, и в этой статье будут представлены некоторые из них. cargo new создает новый проект (и папку) с именем notes (или любым другим, указанным вами) в вашем текущем каталоге.

src/main.rs включает очень простое приложение «Hello World».

Cargo.toml называется манифестом. Подробнее о том, что здесь поддерживается, см. в The Cargo Book. Нам нужно сделать только одну небольшую настройку, чтобы добавить зависимость, так что не беспокойтесь слишком много о том, чтобы понять все.

.gitignore удобно игнорировать папку target по умолчанию из git.

Запустите приложение привет мир

cargo run

Команда cargo run собирает и запускает текущий пакет. После внесения изменений в этом руководстве вы будете использовать его для запуска и тестирования этих изменений. Запустив его один раз, вы увидите еще пару файлов и папок.

Cargo.lock Это автоматически сгенерированный файл, в котором точно указано, какие версии библиотек используются. Подробнее о Cargo.toml vs. Cargo.lock читайте в The Cargo Book.

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

Подготовка модели данных

Создание базы данных sqlite3

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

Для целей этого урока наша схема очень проста. Нам нужна таблица notes, в которой есть столбец id и столбец body. id хранит уникальный идентификатор конкретной заметки. Это обязательное поле для большинства баз данных, поскольку оно позволяет напрямую ссылаться на существующий элемент. В столбце body будет храниться содержимое заметки, которую мы сохраняем. Здесь вы можете выбрать другой термин, который вам больше нравится. Некоторые возможные варианты: content, message, text или title. Конечно, это можно изменить постфактум, но с течением времени это становится все труднее, и все больше кода ссылается на эти конкретные термины, поэтому постарайтесь выбрать тот, с которым вы сможете жить, и придерживайтесь его.

Большинство учебных пособий, которые я вижу в настоящее время, в основном касаются хранения данных на сервере и их синхронизации через какой-либо API. Эта серия в конечном итоге появится, но очень важно, чтобы приложение для заметок, подобное тому, которое мы создаем, работало в автономном режиме. Раннее размещение этого ограничения позволяет нам строить с учетом оффлайна, а не пытаться прикрутить его к существующему облачному приложению.

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

Создание таблицы заметок

Первый шаг — создать таблицу, в которой будут храниться данные наших приложений. Мы будем использовать библиотеку rusqlite для обработки нашего подключения к базе данных sqlite. Вы можете установить его, изменив файл Cargo.toml.

Cargo.toml

[package]
name = "notes"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies.rusqlite]
version = "0.26.1"
features = ["bundled"]

Это добавляет rusqlite в качестве зависимости, которая будет установлена ​​при следующей попытке сборки. features = [“bundled”] говорит пакету скомпилировать SQLite . Это особенно полезно в Windows, где найти системные библиотеки на удивление сложно.

После того, как это было добавлено, вы теперь можете получить доступ к rusqlite в своем main.rs, заменив существующий код следующим:

use rusqlite::{Connection, Result};
fn main() -> Result<(), Box<dyn std::error::Error>> {
  let conn = Connection::open("notes.db")?;
  conn.execute(
    "create table if not exists notes (
      id integer primary key,
      body text not null unique
    )",
    [],
  )?;
  
  Ok(())
}

Теперь вы можете запустить cargo run, и как только программа заметок будет собрана, она запустится и немедленно завершится. Затем вы должны увидеть файл в вашем текущем каталоге с именем notes.db. Если вы установили DB Browser, вы можете открыть этот файл и увидеть, что существует таблица notes со столбцами id и body.

Если вы снова запустите программу, ничего не должно произойти. Команда SQL, которую мы запускаем:

create table if not exists notes (
  id integer primary key,
  body text not null unique
)

Указывает создавать таблицу только в том случае, если она еще не существует. Когда мы открываем соединение с let conn = Connection::open(“notes.db”)?;, мы указываем библиотеке rusqlite тот же файл базы данных, и она может определить, что эта таблица уже создана.

[Необязательно] Установите браузер БД для SQLite

Я считаю полезным иметь возможность визуально подтвердить, что все работает так, как задумано. На этом этапе вы можете загрузить браузер базы данных, который позволит вам увидеть содержимое только что созданного файла notes.db.

Вы можете скачать браузер БД для SQLite. После установки откройте его и нажмите Open Database .

Теперь, когда у нас создана таблица и настроена наша схема, мы можем приступить к добавлению нашей первой заметки.

CRUD — создание, чтение, обновление, удаление

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

  1. Создавать
  2. Читать
  3. Удалить
  4. Обновлять

Эта часть руководства будет охватывать только аспект создания. Следующий пойдет за остальными.

Создать

Творчество стоит на первом месте, потому что без него все остальное не имеет смысла. Во многих сценариях ваше приложение становится функциональным, как только становится возможным его создание. Вы, очевидно, хотели бы иметь возможность делать другие, но их отсутствие не мешает вам создавать новые предметы. В нашем примере с заметками вы увидите, что даже если вы только что добавили функцию создания, у вас все еще есть программа, которая правильно сохраняет заметки в локальной базе данных. Если это все, что вам удалось сделать, вы все равно можете открыть базу данных sqlite3 с помощью чего-то вроде DB Browser for SQLite и просматривать там старые заметки.

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

Требования

Для создания заметок я просто хочу иметь возможность ввести свою заметку в терминал и нажать Enter. Чтобы получить ввод из командной строки, мы будем использовать встроенный пакет std::io.

use std::io;
use rusqlite::{Connection, Result};
fn main() -> Result<(), Box<dyn std::error::Error>> {
  let conn = Connection::open("notes.db")?;
  conn.execute(
    "create table if not exists notes (
      id integer primary key,
      body text not null unique
    )",
    [],
  )?;
  let mut buffer = String::new();
  io::stdin().read_line(&mut buffer)?;
  conn.execute("INSERT INTO notes (body) values (?1)", [buffer])?;
  Ok(())
}

Мы можем снова запустить наше приложение с cargo run, и теперь вы должны увидеть, что оно не закрывается сразу. Вы можете ввести любое сообщение, которое вам нравится, но «привет, мир» — это стандартное сообщение «эта штука работает». Как только вы нажмете Enter, программа должна выйти.

Если вы установили DB Browser выше, теперь вы можете нажать Browse Data, и вы увидите одну строку с id: 1 и body: «hello world» (или что-то еще, что вы только что набрали).

Это довольно хорошо, но цель программы — позволить нам быстро делать много заметок. Нам нужен способ сделать так, чтобы программа не закрывалась сразу после отправки первой заметки.

Для этого мы будем использовать цикл loop, а именно цикл while.

use std::io;
use rusqlite::{Connection, Result};
fn main() -> Result<(), Box<dyn std::error::Error>> {
  let conn = Connection::open("notes.db")?;
  conn.execute(
    "create table if not exists notes (
      id integer primary key,
      body text not null unique
    )",
    [],
  )?;
  let mut running = true;
  while running == true {
    let mut buffer = String::new();
    io::stdin().read_line(&mut buffer)?;
    let trimmed_body = buffer.trim();
    if trimmed_body == "" {
      running = false;
    } else {
      conn.execute("INSERT INTO notes (body) values (?1)", [trimmed_body])?;
    }
  }
  Ok(())
}

Для этого мы вводим переменную boolean с именем running. Когда мы запускаем программу, мы хотим продолжать принимать ввод, поэтому мы инициализируем ее значением true.

let trimmed_body = buffer.trim(); удаляет все пробелы в конце строки ввода. Это необходимо, потому что read_line возвращает строку с символом новой строки \n. Это невидимо в большинстве мест, где вы можете его увидеть, но необходимо, чтобы проверка на равенство trimmed_body == “” работала. В качестве дополнительного преимущества .trim() гарантирует, что все конечные пробелы будут удалены перед сохранением в базе данных.

Теперь мы можем запустить снова с cargo run, и теперь вы сможете вводить ноту за нотой без выхода из программы. После того, как вы закончите писать заметки, вы можете нажать Enter с пустой строкой, и программа закроется.

Подведение итогов

Как упоминалось в начале, этот пост является началом более длинной серии, в которой рассказывается, как создать и спроектировать приложение для заметок для командной строки с использованием Rust. В течение последнего года я использовал простое приложение для заметок в качестве проекта, который я использую для экспериментов с новой технологией. До сих пор я создавал приложения для заметок (ласково называемые энграммами) с использованием Swift iOS, React Native for Android, React и Vanilla JavaScript, используя аналогичную методологию, описанную выше.

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

Если у вас возникнут какие-либо проблемы с какой-либо частью этого руководства, оставьте комментарий, и я могу обновить содержание, чтобы оно было более понятным. Если вам нравится, куда это движется, не забудьте подписаться на меня здесь, в среде или в Твиттере, чтобы получать уведомления о выпуске будущих частей.