🐫

【Rust】親子関係にない別ディレクトリにあるmoduleを参照したい

2022/05/13に公開

概要

最近ようやくRustを触りはじめて、まず躓いたのがディレクトリ間のmoduleのインポートです。JavaScriptのような雰囲気で相対パスでimportできると思ったら、意外にハマりました。
今回はやりたいことと、どう対応したのかというのを書いてみたいと思います。

やりたいこと

下記のようなディレクトリ構成を想定していて、controller→service→modelの形で、親子関係にないmoduleを参照させたいです。

対応方法

stackoverflowの記事How do you use parent module imports in Rust?で、対応方法紹介されていますが、今回はmain.rsでmoduleを定義する方法を試してみました。

実装サンプル

本筋とはあまり関係ないですが、actix-webでAPIを実装する前提とします。
まずはmain.rsで参照するmoduleを定義します。

main.rs
use actix_cors::Cors;
use actix_web::http;
use actix_web::App;
use actix_web::HttpServer;

mod controller {
    pub mod search_controller;
}

mod service {
    pub mod search_service;
}

mod model {
    pub mod api {
        pub mod search_condition_response;
    }
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        let cors = Cors::default()
            .allowed_origin("http://localhost:19006")
            .allowed_methods(vec!["GET", "POST", "PUT", "OPTIONS", "DELETE"])
            .allowed_header(http::header::CONTENT_TYPE);
        App::new()
            .wrap(cors)
	    // ここでcontrollerを参照してる
            .service(controller::search_controller::get_search_condition)
    })
    .bind("0.0.0.0:8080")?
    .run()
    .await
}

次にcontrollerの実装です。controllerではserviceのmoduleを参照します。

search_controller.rs
use crate::service::search_service;
use actix_web::{get, HttpResponse, Responder};

#[get("/get_search_condition")]
pub async fn get_search_condition() -> impl Responder {
    HttpResponse::Ok()
        .content_type("application/json")
	// ここでserviceを参照してる
        .json(search_service::get_search_condition_response())
}

次にserviceの実装です。serviceではmodelのmoduleを参照します。

search_service.rs
use crate::model::api::search_condition_response::SearchConditionResponse;
use crate::model::api::search_condition_response::SearchConditionResponseKV;

pub fn get_search_condition_response() -> SearchConditionResponse {
    // ここでmodelを参照してる
    return SearchConditionResponse {
        category_type: vec![SearchConditionResponseKV {
            key: "category1".to_string(),
            value: "カテゴリー1".to_string(),
        }],
        location: vec![SearchConditionResponseKV {
            key: "tokyo".to_string(),
            value: "東京".to_string(),
        }],
    };
}

最後にmodelです。

search_condition_response.rs
use serde::Serialize;

#[derive(Serialize)]
pub struct SearchConditionResponse {
    pub store_type: Vec<SearchConditionResponseKV>,
    pub location: Vec<SearchConditionResponseKV>,
}

#[derive(Serialize)]
pub struct SearchConditionResponseKV {
    pub key: String,
    pub value: String,
}

Discussion