작업 모듈 만들기
tasks
모듈은 우리의 작업을 나타내며, 이를 저장하고 액세스하는 방법을 나타냅니다.
src 디렉터리에 tasks.rs
라는 새 파일을 만듭니다. 먼저 프로그램에서 할 일 항목을 표시하는 방법을 나타내는 간단한 구조체를 해당 파일 내에서 정의하겠습니다.
use chrono::{DateTime, Utc};
#[derive(Debug)]
pub struct Task {
pub text: String,
pub created_at: DateTime<Utc>,
}
구조체에는 두 개의 필드가 있습니다.
text
는"pay the bills"
와 같이 작업 설명을 저장합니다.created_at
은 작업을 만들 때의 타임스탬프를 저장합니다.
할 일 목록을 작업 벡터(Vec<Task>
)로 표시하기 때문에 status
또는 is_complete
필드를 추가하지 않습니다. 따라서 작업이 완료되면 벡터에서 제거하면 됩니다.
현재 타사 크레이트 chrono
를 사용하고 있다는 것을 알았을 것입니다. DateTime
구조체의 Utc
매개 변수를 지정했습니다. chrono
는 Rust에서 날짜와 시간 데이터를 처리해야 하는 경우 사용하기에 좋은 크레이트입니다. 이는 순간을 표현하기 위한 쉬운 API를 제공합니다.
현재 사용하고 있기 때문에 Cargo.toml
파일에서 이를 선언해야 합니다.
[dependencies]
chrono = "0.4"
structopt = "0.3"
다음 단계는 새 작업을 인스턴스화하는 방법을 구현하는 것입니다. 작업에는 항상 현재 날짜와 시간으로 타임스탬프가 지정됩니다. Task
구조체 뒤에 다음 코드를 추가합니다.
impl Task {
pub fn new(text: String) -> Task {
let created_at: DateTime<Utc> = Utc::now();
Task { text, created_at }
}
}
이 코드는 Task::new
함수를 정의합니다. 함수에는 작업 설명만 필요합니다. Utc::now()
메서드를 사용하여 현재 타임스탬프를 캡처합니다.
작업 구조체가 완료된 것 같습니다. 이제 이 모듈의 다음 항목인 지속성에 대해 살펴보겠습니다.
할 일 목록을 작업 벡터로 나타내기 때문에 JSON 파일을 사용하여 데이터를 쉽게 유지할 수 있습니다. 이를 위해 Rust 생태계의 또 다른 우수한 크레이트 serde_json
을 사용하는 것이 가장 좋습니다.
serde_json
을 사용한 작업을 직렬화 및 역직렬
계속하기 전에 Rust에서 인코딩 및 디코딩을 위한 몇 가지 권장 사례를 다루어야 합니다.
구조체와 열거형 인스턴스를 유지해야 하는 경우 직렬화에 대해 생각해야 합니다. 해당 데이터를 다시 프로그램으로 가져와야 하는 경우 역직렬화에 대해 언급해야 합니다.
직렬화 및 역직렬화는 데이터를 바이트 스트림으로 저장한 다음, 나중에 사용하기 위해 정보를 손실하지 않고 검색하는 프로세스입니다. 그런 다음, 연결을 통해 해당 바이트를 전송하거나 스토리지 디바이스의 파일에 저장할 수 있습니다. 이 OWASP 치트시트에서 직렬화 및 역직렬화에 대해 자세히 알아볼 수 있습니다.
Rust 커뮤니티는 Rust 데이터 구조의 대부분의 직렬화 및 역직렬화를 효율적이고 일반적으로 처리할 수 있는 serde
크레이트를 권장합니다. 기존의 이 크레이트를 사용함으로써 훨씬 더 생산적이고 자연스러울 수 있습니다.
Task
유형 직렬화를 시작하려면 다음 두 개의 크레이트를 사용해야 합니다.
serde
.Serialize
및Deserialize
특성을 도출할 수 있는 기본 크레이트입니다.serde_json
. 이러한 특성을 선택한 파일 사양 형식인 JSON으로 구현하는 크레이트입니다.
항상 첫 번째 단계는 Cargo.toml
파일의 [dependencies]
섹션에 serde_json
및 serde
를 포함하는 것입니다. 이번에는 일부 serde
기능을 조건부로 컴파일해야 하므로 다른 표기법을 사용하여 지정합니다. 이제 파일이 다음과 같이 표시됩니다.
[dependencies]
serde_json = "1.0" # Add serde_json.
structopt = "0.3"
[dependencies.chrono]
features = ["serde"] # We're also going to need the serde feature for the chrono crate, so we can serialize the DateTime field.
version = "0.4"
[dependencies.serde] # Add serde in its own section.
features = ["derive"] # We'll need the derive feature.
version = "1.0"
이제 serde
의 새로운 기능을 사용하도록 Task
구조체를 조정할 수 있습니다. tasks.rs
파일을 열고 다음과 같이 구조체를 수정합니다.
use chrono::{serde::ts_seconds, DateTime, Local, Utc};
use serde::Deserialize;
use serde::Serialize;
#[derive(Debug, Deserialize, Serialize)]
pub struct Task {
pub text: String,
#[serde(with = "ts_seconds")]
pub created_at: DateTime<Utc>,
}
차이점은 다음과 같습니다.
- 구현할 특성 목록에
Deserialize
및Serialize
를 추가했습니다. created_at
필드에 주석을 달아ts_seconds
를chrono
에서serde(with = ...)
특성으로 전달하여chrono
가Datetime
유형이 두 가지 새로운 특성을 구현하는 방법을serde
에 알릴 수 있도록 했습니다.
이제 Task
유형에서 직렬화와 역직렬화를 모두 수행할 수 있으므로 파일 처리 함수를 계속하여 구현할 수 있습니다.
파일 시스템과 상호 작용
프로그램에서 수행해야 하는 세 가지 작업을 검토해 보겠습니다.
- 할 일 목록에 새 작업을 추가합니다.
- 이 목록에서 완료된 작업을 제거합니다.
- 목록의 모든 현재 작업을 인쇄합니다.
모듈 인터페이스는 목록만큼 간단해야 하므로 각 작업에 하나씩, 세 가지 함수를 사용하겠습니다.
use std::io::Result;
use std::path::PathBuf;
pub fn add_task(journal_path: PathBuf, task: Task) -> Result<()> { ... }
pub fn complete_task(journal_path: PathBuf, task_position: usize) -> Result<()> { ... }
pub fn list_tasks(journal_path: PathBuf) -> Result<()> { ... }
먼저 각 함수의 시그니처를 살펴보세요. 이들 모두에는 journal_path: PathBuf
인수가 필요합니다. 그 이유는 이들 모두가 작업을 완료하기 위해 파일 경로(작업이 저장되는 파일의 경로)가 필요하기 때문입니다.
add_task
에는Task
인수도 필요합니다. 이 인수는 목록에 추가될 작업을 지정합니다.complete_task
는 제거할Task
를 표시하기 위해task_position
인수가 필요합니다. 작업이 제거되면 이는 완료된 것입니다.list_tasks
에는 추가 정보가 필요하지 않습니다. 현재 저널 파일에 저장된 모든 작업을 보기 쉬운 형식으로 사용자에게 제공합니다.
함수에는 모두 동일한 반환 형식인 std::io::Result<()>
이 있습니다. 이 형식은 반환 형식이 I/O 결과임을 나타냅니다. 이 반환 형식은 물리적 단어로 데이터를 다룰 때 발생할 수 있는 원치 않는 결과가 광범위하게 나타날 것으로 예상하고 있음을 나타냅니다. Ok
변형은 빈 튜플 ()
에 불과하며, 일반적으로 데이터가 전혀 없는 유형입니다. 이는 함수가 Ok
를 반환했지만 오류가 발생하지 않았음을 알리는 것입니다.
다음 세 단원에서는 각 함수의 콘텐츠를 자세히 작성하는 방법을 살펴보겠습니다.