Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
19
Help us understand the problem. What is going on with this article?
@yosqueoy

Rustコードリーディング - ripgrep

ripgrepを読む

Rust初心者が有名ツールripgrepのソースコードをざっと読んで気づいたこと。
個人的な気づきなので、Rust上級者には当たり前に感じられると思う。
実装には立ち入らず、全体的なコードのスタイルを眺めただけ。

lazy_staticマクロはトップレベルだけではなく関数内でも使える

lazy_static!はトップレベルでしか使えないと誤解していたが、実は関数内でも使うことができるようだ。関数内で使うと定義されるstatic変数のスコープを制限でき、初期化も初回関数呼び出しまで遅らせられる。

pub fn parse_human_readable_size(size: &str) -> Result<u64, ParseSizeError> {
    lazy_static! {
        static ref RE: Regex = Regex::new(r"^([0-9]+)([KMG])?$").unwrap();
    }

    let caps = match RE.captures(size) { ...

ソース箇所

この動作については、以下のフォーラムで詳しく説明されている。

newメソッドは対象の型そのものではなくResultを返してもよい

個人的な慣例として、対象の型そのものを返すときのみnewと命名し、Resultを返すときはtry_newとかloadとかに改名していたが、newのままでよいことが判明。

impl Glob {
    pub fn new(glob: &str) -> Result<Glob, Error> {
...

ソース箇所

オフィシャルクレートのregexでもRegex::newはResultを返す。

Impl内にはJava/C#で言うprivate staticユーティリティメソッドを定義しない

JavaやC#では、そのクラス内でしか使わない共通ロジックはprivate staticのユーティリティメソッドとして書くことが多いが、Rustではトップレベル(モジュールレベル)に関数を定義できるため、private staticメソッド(Rustではプライベート関連関数)を使う理由がない。

実際ripgrepのソースコードでは、implされる型に関係のないメソッドはトップレベルに配置されている。関連関数はほぼ全てコンストラクタか型変換目的の様子。

さらに、特定のメソッド内でだけ使われる共通処理は関数内関数として定義されている。

参考:フォーラムでもモジュールレベル関数と関連関数の使い分けについて質問が回答されている。

パターンマッチでマッチする可能性のないアームにはunreachableマクロを使う

絶対にマッチしないとわかっているアームには生のpanic!を使っていたが、std::unreachableマクロがその目的で用意されていることが判明。

    let bytes = match suffix {
        "K" => value.checked_mul(1 << 10),
        "M" => value.checked_mul(1 << 20),
        "G" => value.checked_mul(1 << 30),
        // Because if the regex matches this group, it must be [KMG].
        _ => unreachable!(),
    };

ソース箇所

パターンマッチのアームの順番

(どうでもよいことだと思いつつも)パターンマッチのSome/Noneの順番を統一すべきか時折迷う。(Scalaなど他言語も含め)
ripgrepでは特に統一されておらず、ロジック的に書きやすい順番にしている模様。

pub fn parse_human_readable_size(size: &str) -> Result<u64, ParseSizeError> {
    ...
    // Someが先
    let caps = match RE.captures(size) {
        Some(caps) => caps,
        None => return Err(ParseSizeError::format(size)),
    };
    ...
    // Noneが先
    let suffix = match caps.get(2) {
        None => return Ok(value),
        Some(cap) => cap.as_str(),
    };
    ...
}

ソース箇所

19
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
yosqueoy

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
19
Help us understand the problem. What is going on with this article?