既存のクレートを改造して使う

この記事は[Rust 2 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/rust2) の16日目の記事です。

# はじめに
[crates.io](https://crates.io/) では様々なクレートが配布されています。Rust でコードを書いていると、既存のクレートの機能がイマイチで動作を書き換えたくなることがあります。このようなときは、cargo の patch 機能を使うと便利です。

この記事の内容は以下の環境で検証しました: 

```
stable-x86_64-apple-darwin (default)
rustc 1.48.0 (7eac88abb 2020-11-16)
```

# rand クレートの動作を書き換える
乱数を生成するクレート、 rand の動作を書き換えてみましょう。

rand では確率分布を指定することができるのですが、`rand::distributions::Alphanumeric` を使うとランダムな ASCII 文字列を生成することができます。例えば、このようなコードを書くと

```rust

use std::iter;
use rand::{Rng, thread_rng};
use rand::distributions::Alphanumeric;

fn main() {
    let mut rng = thread_rng();
    let chars: String = iter::repeat(())
            .map(|()| rng.sample(Alphanumeric))
            .take(7)
            .collect();
    println!("Random chars: {}", chars);
}
```

このような結果が得られます: 

```
Random chars: rVZ27tb
```

[Playground で実行する](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=87c5a5c6aa2df71985bc99b8866f869d)

このコードで得られる文字列は `a-z, A-Z, 0-9` の範囲から文字がランダムに選ばれます。ここで、数字 `0-9` が選ばれるのが気に入らなかったとしましょう。`a-z, A-Z` のみの範囲から文字が選ばれるようにしたいと思います。


## STEP1: rand クレートのリポジトリを fork して編集する
まずは [rand クレートのリポジトリ](https://github.com/rust-random/rand) を fork して改造します。[このあたりのコード](https://github.com/rust-random/rand/blob/6682db1d4da701db1c094b874c37b7cedc6c6123/src/distributions/other.rs#L88-L90) を書き換えればよさそうですね

```diff:src/distributions/other.rs

impl Distribution<u8> for Alphanumeric {
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> u8 {
-        const RANGE: u32 = 26 + 26 + 10;
+        const RANGE: u32 = 26 + 26;
        const GEN_ASCII_STR_CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
-                abcdefghijklmnopqrstuvwxyz\
-                0123456789";
+                abcdefghijklmnopqrstuvwxyz";
        // We can pick from 62 characters. This is so close to a power of 2, 64,
        // that we can do better than `Uniform`. Use a simple bitshift and
        // rejection sampling. We do not use a bitmask, because for small RNGs
        // the most significant bits are usually of higher quality.
        loop {
            let var = rng.next_u32() >> (32 - 6);
            if var < RANGE {
                return GEN_ASCII_STR_CHARSET[var as usize];
            }
        }
    }
}
```

crates.io で公開されている最新バージョンは `0.7.3` だったので、`0.7.3` タグから `patched` ブランチを生やし、コミットして push しておきました。できたものがこちらになります [Kumassy/qiita-patched-rand](https://github.com/Kumassy/qiita-patched-rand/tree/patched)

## STEP2: 改造したクレートを使う
改造した rand クレートを利用するアプリケーションを適当に作ります。

```bash
$ cargo new --bin qiita-patch-crate
```

としてプロジェクトの雛形を作り、Cargo.toml を編集します。`dependencies` セクションには通常通り `rand` を記載しますが、`patch.crates-io` セクションには改造した rand クレートのリポジトリを記載します

```toml:Cargo.toml
[package]
name = "qiita-patch-crate"
version = "0.1.0"
authors = ["Kumassy"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rand = "0.7.3"

[patch.crates-io]
rand = { git = 'https://github.com/Kumassy/qiita-patched-rand', branch = 'patched' }
```

`src/main.rs` を適当に編集しましょう

```rust:src/main.rs
use std::iter;
use rand::{Rng, thread_rng};
use rand::distributions::Alphanumeric;

fn main() {
    let mut rng = thread_rng();
    let chars: String = iter::repeat(())
            .map(|()| rng.sample(Alphanumeric))
            .take(7)
            .collect();
    println!("Random chars: {}", chars);
}
```

これを実行すると、数字 `0-9` を含まないランダムな ASCII 文字列が得られるようになります: 

```bash
$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.04s
     Running `target/debug/qiita-patch-crate`
Random chars: "HRHKpIq
```

できたものはこちらにあります
[Kumassy/qiita-patch-crate](https://github.com/Kumassy/qiita-patch-crate)

# まとめ
Cargo.toml の `patch` セクションを使うことで、既存のクレートの動作を書き換えることができました。crates.io にまだ公開されてないバージョンのクレートを試したり、クレートのバグフィックスを検証したりするときにも便利そうですね。


# もっと詳しく
The Rust Book に詳しい解説があるので、こちらもあわせてご覧ください

> Overriding Dependencies
> https://doc.rust-lang.org/cargo/reference/overriding-dependencies.html