작업 모듈 만들기

완료됨

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. SerializeDeserialize 특성을 도출할 수 있는 기본 크레이트입니다.
  • serde_json. 이러한 특성을 선택한 파일 사양 형식인 JSON으로 구현하는 크레이트입니다.

항상 첫 번째 단계는 Cargo.toml 파일의 [dependencies] 섹션에 serde_jsonserde를 포함하는 것입니다. 이번에는 일부 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>,
}

차이점은 다음과 같습니다.

  • 구현할 특성 목록에 DeserializeSerialize를 추가했습니다.
  • created_at 필드에 주석을 달아 ts_secondschrono에서 serde(with = ...) 특성으로 전달하여 chronoDatetime 유형이 두 가지 새로운 특성을 구현하는 방법을 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를 반환했지만 오류가 발생하지 않았음을 알리는 것입니다.

다음 세 단원에서는 각 함수의 콘텐츠를 자세히 작성하는 방법을 살펴보겠습니다.