競技プログラミング用にRustコードを一つの.rsファイルにバンドルするCargoサブコマンドです。
更新情報はCHANGELOG.mdにあります。
```toml [package] name = "solve" version = "0.0.0" edition = "2018"
[dependencies] ac-library-rs-parted = { git = "https://github.com/qryxip/ac-library-rs-parted" } ac-library-rs-parted-convolution = { git = "https://github.com/qryxip/ac-library-rs-parted" } ac-library-rs-parted-dsu = { git = "https://github.com/qryxip/ac-library-rs-parted" } ac-library-rs-parted-fenwicktree = { git = "https://github.com/qryxip/ac-library-rs-parted" } ac-library-rs-parted-lazysegtree = { git = "https://github.com/qryxip/ac-library-rs-parted" } ac-library-rs-parted-math = { git = "https://github.com/qryxip/ac-library-rs-parted" } ac-library-rs-parted-maxflow = { git = "https://github.com/qryxip/ac-library-rs-parted" } ac-library-rs-parted-mincostflow = { git = "https://github.com/qryxip/ac-library-rs-parted" } ac-library-rs-parted-modint = { git = "https://github.com/qryxip/ac-library-rs-parted" } ac-library-rs-parted-scc = { git = "https://github.com/qryxip/ac-library-rs-parted" } ac-library-rs-parted-segtree = { git = "https://github.com/qryxip/ac-library-rs-parted" } ac-library-rs-parted-string = { git = "https://github.com/qryxip/ac-library-rs-parted" } ac-library-rs-parted-twosat = { git = "https://github.com/qryxip/ac-library-rs-parted" } qryxip-competitive-fastout = { git = "https://github.com/qryxip/competitive-programming-library" } qryxip-competitive-input = { git = "https://github.com/qryxip/competitive-programming-library" } qryxip-competitive-tonelli-shanks = { git = "https://github.com/qryxip/competitive-programming-library" }
```
```rust
extern crate fastout as _;
extern crate input as _;
use aclmodint::ModInt; use tonellishanks::ModIntBaseExt as _;
fn main() { input! { yps: [(u32, u32)], }
for (y, p) in yps {
ModInt::set_modulus(p);
if let Some(sqrt) = ModInt::new(y).sqrt() {
println!("{}", sqrt);
} else {
println!("-1");
}
}
} ```
↓
console
❯ cargo equip --resolve-cfgs --remove comments docs --rustfmt --check --bin solve | xsel -b
Submit Info #40609 - Library-Checker
nightlyツールチェインとcargo-udepsもインストールしてください。
console
❯ rustup update nightly
console
❯ cargo install cargo-udeps
console
❯ cargo install cargo-equip
masterブランチからconsole
❯ cargo install --git https://github.com/qryxip/cargo-equip
バイナリでの提供もしています。
cargo-equipで展開できるライブラリには以下の制約があります。
各crate rootには#[macro_export]したマクロと同名なアイテムが存在しないようにする。
cargo-equipはmod lib_name直下にpub use crate::{ それらの名前 };を挿入するため、展開後のuseで壊れます。
bin/example側ではマクロは#[macro_use]で使ってください。
```rust // in main source code
extern crate input as _; ```
bin/example内のextern crateはコメントアウトされます。
```rust // in main source code
/#[macro_use]
extern crate input as _;/ // as _でなければuse crate::$name;が挿入される
```
Rust 2015に展開する場合のみ、共に展開する予定のクレートを使うときにextern preludeから直接名前を解決しない。
ルートモジュール以外のモジュールでextern crateを宣言してマウントし、そこを相対パスで参照してください。
cargo-equipは--exclude <SPEC>..., --exclude-atcoder-crates, --exclude-codingame-cratesで指定されたクレートを除いて、
extern crateをuse crate::extern_crate_name_in_main_crate;に置き換えます。
lib同士をexter crateで参照する場合、誤って直接使わないように対象の名前はリネームしておくことを強く推奨します。
```diff mod extern_crates {
pub(super) use crate::another_lib; }
use self::externcrates::anotherlib::foo::Foo; // Prepend self:: to make compatible with Rust 2015
```
AOJ ~~やyukicoder~~ 等のRustが2018が利用できないサイトにこのツールを使用しないなら不要です。
2018向けにはcargo-equipは各ライブラリにこのようなmod __pseudo_extern_preludeを作り、extern preludeの代用にします。
このmod __pseudo_extern_prelude自体はRust 2015でもコンパイルできますが、Rust 2015はuse another_lib::A;を解決できません。
```diff +mod _pseudoextern_prelude {
マクロ内ではcrateではなく$crateを使う。
macro_rules!内の$crateは$crate::extern_crate_name_in_main_crateに置き換えられます。
macro_rules!内のcrateは置き換えられません。
3.以外の場合も可能な限り絶対パスを使わない。
cargo-equipはpathのcrateはcrate::extern_crate_name_in_main_crateに、pub(crate)はpub(in crate::extern_crate_name_in_main_crate)に置き換えます。
ただしこの置き換えは必ず上手くいくかどうかがわかりません。
できる限りcrate::よりもself::とsuper::を使ってください。
diff
-use crate::foo::Foo;
+use super::foo::Foo;
可能な限りライブラリを小さなクレートに分割する。
cargo-equipは「クレート内のアイテムの依存関係」を調べることはしません。 AtCoder以外に参加する場合は、出力結果を制限内(たいてい64KiB程度)に収めるためにできるだけ小さなクレートに分割してください。
console
.
├── input
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── output
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
⋮
ライブラリが用意できたら、それらをbin/example側のCargo.tomlの[dependencies]に加えてください。
コンテスト毎にツールでパッケージを自動生成しているならそれのテンプレートに加えてください。
rust-lang-ja/ac-library-rsを使いたい場合、qryxip/ac-library-rs-partedを使ってください。
本物のac-library-rsを ~~custom-build内で自動で加工する~~ スクリプトで加工したクレートです。
~~custom-build部分はAtCoder環境と同様のCargo.lockを壊さないためにsyn 1.0.17とproc-macro2 1.0.10で書かれています。~~
やっぱり小さいといってもdependencyが数十個付いてきてCI等で煩わしいのでやめました。
現在のこれらのクレートは外部の依存クレートを持たず、瞬時にビルド可能です。
toml
[dependencies]
ac-library-rs-parted = { git = "https://github.com/qryxip/ac-library-rs-parted" }
ac-library-rs-parted-convolution = { git = "https://github.com/qryxip/ac-library-rs-parted" }
ac-library-rs-parted-dsu = { git = "https://github.com/qryxip/ac-library-rs-parted" }
ac-library-rs-parted-fenwicktree = { git = "https://github.com/qryxip/ac-library-rs-parted" }
ac-library-rs-parted-lazysegtree = { git = "https://github.com/qryxip/ac-library-rs-parted" }
ac-library-rs-parted-math = { git = "https://github.com/qryxip/ac-library-rs-parted" }
ac-library-rs-parted-maxflow = { git = "https://github.com/qryxip/ac-library-rs-parted" }
ac-library-rs-parted-mincostflow = { git = "https://github.com/qryxip/ac-library-rs-parted" }
ac-library-rs-parted-modint = { git = "https://github.com/qryxip/ac-library-rs-parted" }
ac-library-rs-parted-scc = { git = "https://github.com/qryxip/ac-library-rs-parted" }
ac-library-rs-parted-segtree = { git = "https://github.com/qryxip/ac-library-rs-parted" }
ac-library-rs-parted-string = { git = "https://github.com/qryxip/ac-library-rs-parted" }
ac-library-rs-parted-twosat = { git = "https://github.com/qryxip/ac-library-rs-parted" }
準備ができたらコードを書いてください。
bin/example側の制約は以下の2つです。
useしない。qualified pathで使うか#[macro_use]で使う。bin/example内にmodを作る場合、その中ではextern preludeから展開予定のライブラリの名前を解決しない。```rust
extern crate input as _;
use std::io::Write as _;
fn main() { input! { n: usize, }
buffered_print::buf_print(|out| {
macro_rules! println(($($tt:tt)*) => (writeln!(out, $($tt)*).unwrap()));
for i in 1..=n {
match i % 15 {
0 => println!("Fizz Buzz"),
3 | 6 | 9 | 12 => println!("Fizz"),
5 | 10 => println!("Buzz"),
_ => println!("{}", i),
}
}
});
} ```
コードが書けたらcargo equipで展開します。
--bin {binの名前}か--example {exampleの名前}、または--src {binのファイルパス}でbin/exampleを指定してください。
パッケージ内のbin/exampleが一つの場合は省略できます。
ただしdefault-runには未対応です。
console
❯ cargo equip --bin "$name"
コードはこのように展開されます。
extern_crate_nameがbin/example側から与えられていないクレートは__package_name_0_1_0のような名前が与えられます。
``diff
+//! # Bundled libraries
+//!
+//! -qryxip-competitive-buffered-print 0.0.0 (path+█████████████████████████████████████████████████████████████████████████████████████)published in https://github.com/qryxip/competitive-programming-library licensed underCC0-1.0ascrate::buffered_print
+//! -qryxip-competitive-input 0.0.0 (path+████████████████████████████████████████████████████████████████████████████)published in https://github.com/qryxip/competitive-programming-library licensed underCC0-1.0ascrate::input`
-#[macrouse] -extern crate input as _; +/*#[macrouse] +extern crate input as _;*/
use std::io::Write as _;
fn main() { input! { n: usize, }
buffered_print::buf_print(|out| {
macro_rules! println(($($tt:tt)*) => (writeln!(out, $($tt)*).unwrap()));
for i in 1..=n {
match i % 15 {
0 => println!("Fizz Buzz"),
3 | 6 | 9 | 12 => println!("Fizz"),
5 | 10 => println!("Buzz"),
_ => println!("{}", i),
}
}
});
}
+
+// The following code was expanded by cargo-equip.
+
+#[allow(deadcode)]
+mod bufferedprint {
+ // ...
+}
+
+#[allow(dead_code)]
+mod input {
+ // ...
+}
```
cargo-equipがやる操作は以下の通りです。
bin/example側
#![cfg_attr(cargo_equip, cargo_equip::skip)]を発見した場合、以下の処理をスキップして--checkの処理だけ行い出力mod $name;をすべて再帰的に展開する。このとき各モジュールをインデントする。ただし複数行にまたがるリテラルが無い場合はインデントしないextern crateを処理libを下部に追加lib側
mod $name;をすべて再帰的に展開するcrateを処理extern crateを処理macro_rules!を処理mod __pseudo_extern_prelude { .. }とuse (self::|$(super::)*)__pseudo_extern_prelude::*;を挿入--resolve-cfgオプションを付けた場合、#[cfg(常にTRUEのように見える式)]のアトリビュートと#[cfg(常にFALSEのように見える式)]のアトリビュートが付いたアイテムを消去--remove docsオプションを付けた場合、doc commentを消去--remove commentsオプションを付けた場合、commentを消去--minify allオプションを付けた場合コード全体を最小化する--rustfmtオプションを付けた場合Rustfmtでフォーマットするcargo-equipは手続き型マクロを展開する機能を持っています。
```rust
extern crate memoise as _;
extern crate proconio_derive as _;
fn main() { for i in 0..=100 { println!("{}", fib(i)); } }
fn fib(n: i64) -> i64 { if n == 0 || n == 1 { return n; } fib(n - 1) + fib(n - 2) } ```
↓
Output
``rust
//! # Procedural macros
//!
//! -memoise 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)licensed underBSD-3-Clause
//! -proconio-derive 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)licensed underMIT OR Apache-2.0`
/#[macro_use] extern crate memoise as _;/ /#[macro_use] extern crate proconio_derive as _;/
/#[fastout] fn main() { for i in 0..=100 { println!("{}", fib(i)); } }/ fn main() { let proconiostdout = ::std::io::stdout(); let mut _proconiostdout = ::std::io::BufWriter::new(proconiostdout.lock()); #[allow(unusedmacros)] macrorules ! print { ($ ($ tt : tt) *) => { { use std :: io :: Write as _ ; :: std :: write ! (proconiostdout , $ ($ tt) *) . unwrap () ; } } ; } #[allow(unusedmacros)] macrorules ! println { ($ ($ tt : tt) *) => { { use std :: io :: Write as _ ; :: std :: writeln ! (proconiostdout , $ ($ tt) *) . unwrap () ; } } ; } let _proconiores = { for i in 0..=100 { println!("{}", fib(i)); } }; <::std::io::BufWriter<::std::io::StdoutLock> as ::std::io::Write>::flush( &mut _proconiostdout, ) .unwrap(); return _proconiores; }
/#[memoise(n <= 100)] fn fib(n: i64) -> i64 { if n == 0 || n == 1 { return n; } fib(n - 1) + fib(n - 2) }/ threadlocal ! (static FIB : std :: cell :: RefCell < Vec < Option < i64 > > > = std :: cell :: RefCell :: new (vec ! [None ; 101usize])); fn fibreset() { FIB.with(|cache| { let mut r = cache.borrowmut(); for r in r.itermut() { *r = None } }); } fn fib(n: i64) -> i64 { if let Some(ret) = FIB.with(|cache| { let mut bm = cache.borrowmut(); bm[(n) as usize].clone() }) { return ret; } let ret: i64 = (|| { if n == 0 || n == 1 { return n; } fib(n - 1) + fib(n - 2) })(); FIB.with(|cache| { let mut bm = cache.borrowmut(); bm[(n) as usize] = Some(ret.clone()); }); ret }
// The following code was expanded by cargo-equip.
```
rust-analyzer(.exe)は自動でダウンロードされます。proc-macroクレートは1.47.0以上のRustでコンパイルされる必要があります。
現在のツールチェインが1.47.0未満である場合、1.47.0以上のツールチェインを探してそれでコンパイルします。pub use $name::*;でre-exportedされた手続き型マクロも展開することができます。--resolve-cfgs#[cfg(恒真)] (e.g. cfg(feature = "enabled-feature"))のアトリビュートを消去します。#[cfg(恒偽)] (e.g. cfg(test), cfg(feature = "disable-feature"))のアトリビュートが付いたアイテムを消去します。これは次の割り当てで判定されます。
test: falseproc_macro: falsecargo_equip: truefeature: bin/example側から見て有効化されているもののみtrue```rust
pub mod a { pub struct A;
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
} ```
↓
```rust
pub mod a { pub struct A; } ```
--remove <REMOVE>...--remove docsでDoc comment (//! .., /// .., /** .. */, #[doc = ".."])を--remove commentsでコメント (// .., /* .. */)を除去します。
```rust
pub mod a { //! A.
/// A.
pub struct A; // aaaaa
} ```
↓
```rust
pub mod a { pub struct A; } ```
--minify <MINIFY>--minify libで展開後のライブラリをそれぞれ一行に折り畳みます。
--minify allでコード全体を最小化します。
ただ現段階では実装が適当なのでいくつか余計なスペースが挟まる場合があります。
--rustfmt出力をRustfmtでフォーマットします。
--checkバンドルしたコードを出力する前にtarget directoryを共有した一時パッケージを作り、それの上でcargo checkします。
#![cfg_attr(cargo_equip, cargo_equip::skip)]でスキップした場合も有効です。
console
❯ cargo equip --check -o /dev/null
Running `/home/ryo/.cargo/bin/rustup run nightly cargo udeps --output json -p solve --bin solve`
Checking solve v0.0.0 (/home/ryo/src/local/a/solve)
Finished dev [unoptimized + debuginfo] target(s) in 0.13s
info: Loading save analysis from "/home/ryo/src/local/a/solve/target/debug/deps/save-analysis/solve-4eea33c8603d6001.json"
Bundling the code
Checking cargo-equip-check-output-6j2i3j3tgtugeaqm v0.1.0 (/tmp/cargo-equip-check-output-6j2i3j3tgtugeaqm)
Finished dev [unoptimized + debuginfo] target(s) in 0.11s
MIT or Apache-2.0のデュアルライセンスです。