🦀 RustのAPI Guidelinesを読んでみる

Rustのブログ記事でよく言及されることが多いので、API Guidelinesを読んでみました。
本GuidelinesはRustのlibrary teamによってメンテされているみたいです。

Guidelinesの位置付け

本Guidelinesの位置付けはAboutで述べられています。

These guidelines should not in any way be considered a mandate that crate authors must follow, though they may find that crates that conform well to these guidelines integrate better with the existing crate ecosystem than those that do not.

強制ではないが、Guidelinesに沿っておくことで既存のecosystemとよく馴染むというくらいのニュアンスでしょうか。

Naming

命名規則について。雰囲気で決めていましたがこうやって言語化してくれているのは非常に助かります。
とくに複数人開発で、命名がぶれてきたときに好みではなくこのあたりを参照して議論してみると良いのではないでしょうか。

Casing conforms to RFC 430 (C-CASE)

moduleはsnake_caseで型はUpperCamelCaseのような命名規則について述べられています。
一般的には型の文脈ではUpperCamelCase、値の文脈ではsnake_caseと説明されておりなるほどと思いました。

UUIDやHTMLといったacronymsはUuid,Htmlのように書くように言われています。
Goでは、HTMLのように大文字なので最初は違和感あったのですが、今ではUpperCamelCaseのほうが自然に感じるようになりました。実用的な意味でも、コード生成の文脈ではUpperCamelCaseのほうがよいと考えています。特に多言語やprotobuf,sql等からの変換では、html_parserHTMLParserのようにする必要があり、なんらかの方法でHTMLUUIDを特別扱いする仕組みが必要になり、gormなんかは自前で辞書をメンテしていたりしていました。

意外なのが、専らlibraryを書く人向けのGuidelinesでlibraryのtop levelであるcratesについてはunclearになっていた点でした。(ただし参照されているRFC 430では, snake_case (but prefer single word)とされています)
個人的には、packageは"-"、crateは"_"を使うのがいいかなと思っております。

Ad-hoc conversions follow at_,to_ conventions (C-CONV)

as_,to_,into_の使い分けに関するguide。ownershipと実行コストの観点から使い分けられております。例えば、Pathからstrはborrowed -> borrowedなのですが、OSのpath名はutf8である保証がないので、str変換時に確認処理がはいるので、この変換はPath::to_strになります。
この点を嫌ってか、UTF-8のPath型を提供するcaminoがあります。
caminoでは、Path::to_str() -> Option<&str>が、Utf8Path::as_str() -> &strになっています。

into_についてはcostは高い場合と低い場合両方あり得ます。
例えば、String::into_bytes()は内部的に保持しているVec<u8>を返すだけなので低いのですが、BufWriter::into_inner()はbufferされているdataを書き込む処理が走るので重たい処理になる可能性があります。

また、as_into_は内部的に保持しているデータに変換するので抽象度を下げる働きがある一方で、to_はその限りでないという説明がおもしろかったです。

Getter names follow Rust convention (C_GETTER)

structのfieldの取得は、get_fieldではなく、fieldとそのまま使おうということ。
ただし、Cell::getのようにgetの対象が明確なときは、getを使います。
また、getterの中でなんらかのvalidationをしている際は、unsafe fn get_unchecked(&self)のvariantsを追加することも提案されています。

Methods on collections that produce iterators follow iter,iter_mut,into_iter (C-ITER)

collectionのelementの型がTだったとしたときに、Iteratorを返すmethodのsignatureは以下のようにしようということ。

fn iter(&self) -> impl Iterator<Item = &T> {}
fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {}
fn into_iter(self) -> impl Iterator<Item = T> {}

ただし、このguideは概念的に同種(conceptually homogeneous)なcollectionにあてはまるとされ、strはその限りでないので、iter_ではなく、str::bytesstr::charsが提供されている例が紹介されています。

また、適用範囲はmethodなので、iteratorを返すfunctionには当てはまらないとも書かれています。 加えて、iteratorの型はそれを返すmethodに合致したものにすることもIterator type names match the methods that produce them (C-ITER_TY)で書かれています。(into_iter()IntoIter型を返す)
この点については、existential typeで, impl Iterator<Item=T>のようにして具体型を隠蔽する方法もあると思うのですが、どちらがよいのかなと思いました。

Feature names are free of placeholder words (C_FEATURE)

feature abcuse-abcwith-abcのようにしないこと。
また、featureはadditiveなので、no-abcといった機能を利用しない形でのfeatureにしないこと。

Names use a consistent word order (C-WORD_ORDER)

JoinPathsError, ParseIntErrorのようなエラー型を定義していたなら、addressのparseに失敗した場合のエラーはParseAddrErrorにする。(AddrParseErrorではない)
verb-object-errorという順番にするというguideではなく、crate内で一貫性をもたせようということ。

Interoperability

直訳すると相互運用性らしいのですが、いまいちピンとこず。ecosystemとの親和性みたいなニュアンスなのでしょうか。

Types eagerly implement common traits (C-COMMON_TRAITS)

Rustのorphan ruleによって、基本的にはimplはその型を定義しているcrateか実装しようとしているtrait側になければいけない。
したがって、ユーザが定義した型についてstdで定義されているtraitは当該crateでしか定義できない。
例えば、url::Urlstd::fmt::Displayimplする必要があり、application側でUrlDisplayを定義することはできない。

この点についてはRUST FOR RUSTACEANSでも述べられていました。
featureでserdeのSerialize等を追加できるようにしてあるcrateなんかもあるなーと思っていたら(C-SERDE)で述べられていました。

Conversions use the standard traits From, AsRef, AsMut (C-CONV-TRAITS)

From,TryFrom,AsRef,AsMutは可能なら実装してあるとよい。IntoTryIntoFrom側にblanket implがあるので実装しないこと。
u32u16のように安全に変換できる場合とエラーになる変換がある場合は、それぞれFromTryFromで表現できる。

Collections implement FromIterator and Extend (C-COLLECT)

collectionを定義したら、FromIteratorExtendを定義しておく。

Data structures implement Serde's Serialize, Deserialize (C-SERDE)

data structureの役割を担う型はSerializeDeserializeを実装する。
ある型がdata structureかどうかは必ずしも明確でない場合があるが、LinkedHashMapIpAddrをJsonから読んだり、プロセス間通信で利用できるようにしておくのは理にかなっている。
実装をfeatureにしておくことで、downstream側で必要なときにコストを払うことができるに実装することもできる。

dependencies]
serde = { version = "1.0", optional = true }
pub struct T { /* ... */ }

#[cfg(feature = "serde")]
impl Serialize for T { /* ... */ }

#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for T { /* ... */ }

のようにしたり、deriveを利用する場合は以下のようにできる。

[dependencies]
serde = { version = "1.0", optional = true, features = ["derive"] }
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct T { /* ... */ }

Types are Send and Sync where possible (C-SEND_SYNC)

SendSyncはcompilerが自動で実装するので、必要なら以下のテストを書いておく。

#[test]
fn test_send() {
    fn assert_send<T: Send>() {}
    assert_send::<MyStrangeType>();
}

#[test]
fn test_sync() {
    fn assert_sync<T: Sync>() {}
    assert_sync::<MyStrangeType>();
}

時々こういうtestを見かけていたのですが、guidelineにあったんですね。

Error types are meaningful and well-behaved (C-GOOD-ERR)

Result<T,E>に利用するEには、std::error::ErrorSend,Syncを実装しておくとエラー関連のecosystemで使いやすい。
エラー型として、()は使わない。必要ならParseBoolErrorのように型を定義する。

Binary number types provide Hex, Octal, Binary formatting (C-NUM_FMT)

|&といったbit演算が定義されるようなnumber typeには、std::fmt::{UpperHex, LowerHex,Octal,Binary}を定義しておく。

Generic reader/writer functions take R: Read and W: Write by value (C-RW-VALUE)

read/write処理を行う関数は以下のように定義する。

use std::io::Read;

fn do_read<R: Read>(_r: R) {}

fn main() {
    let mut stdin = std::io::stdin();
    do_read(&mut stdin);
}

これがcompileできるのは、stdで


impl<'a, R: Read + ?Sized> Read for &'a mut R { /* ... */ }

impl<'a, W: Write + ?Sized> Write for &'a mut W { /* ... */ }

が定義されているので、&mut stdinReadになるから。関数側がr: &Rのように参照で定義されていないので、do_read(stdin)のようにmoveさせてしまい、loopで使えなくなるのがNew Rust usersによくあるらしい。
逆に関数側が、fn do_read_ref<'a, R: Read + ?Sized>(_r: &'a mut R)のように宣言されている例もみたりするのですが、このguideに従うかぎは不要ということなのでしょうか。
例えば、prettytable-rs::Table::print

Macros

専ら、declarative macroについて述べられています。

Input syntax is evocative of the output (C-EVOCATIVE)

入力のsyntaxが出力を想起させるという意味でしょうか。
macroを使えば実質的にどんなsyntaxを使うこともできるが、できるだけ既存のsyntaxによせようというもの。
例えば、macroの中でstructを宣言するなら、keywordにstructを使う等。

Item macros compose well with attributes (C-MACRO-ATTR)

macroの中にattributeを書けるようにしておこう。

bitflags! {
    #[derive(Default, Serialize)]
    struct Flags: u8 {
        const ControlCenter = 0b001;
        const Terminal = 0b010;
    }
}

Item macros work anywhere that items are allowed (C-ANYWHERE)

以下のようにmacroがmodule levelでも関数の中でも機能するようにする。

mod tests {
    test_your_macro_in_a!(module);

    #[test]
    fn anywhere() {
        test_your_macro_in_a!(function);
    }
}

Item macros support visibility specifiers (C-MACRO-VIS)

macroでvisibilityも指定できるようにする。

bitflags! {
    pub struct PublicFlags: u8 {
        const C = 0b0100;
        const D = 0b1000;
    }
}

Type fragments are flexible (C-MACRO-TY)

macroで$t:tyのようなtype fragmentを利用する場合は以下の入力それぞれに対応できるようにする。

  • Primitives: u8, &str
  • Relative paths: m::Data
  • Absolute paths: ::base::Data
  • Upward relative paths: super::Data
  • Generics: Vec<String>

boilerplate用のhelper macroと違って外部に公開するmacroは考えること多そうで難易度高そうです。

Documentation

codeのコメント(rustdoc)について。

Crate level docs are thorough and include examples (C-CRATE-DOC)

See RFC 1687と書かれ、PRへリンクされています。
リンクでなく内容を書くべきというissueもたっているようでした。
PR作られていたのがnushell作られているJTさんでした。
内容としてはこちらになるのでしょうか。本家にはまだmergeされていないようでした。

All items have a rustdoc example (C-EXAMPLE)

publicなmodule, trait struct enum, function, method, macro, type definitionは合理的な範囲でexampleを持つべき。
合理的というのは、他へのlinkで足りるならそうしてもよいという意味。
また、exampleを示す理由は、使い方を示すというより、なぜこの処理を使うかを示すかにあるとのことです。

Examples use ?, not try!, not unwrap (C-QUESTION-MARK)

documentのexampleであっても、unwrapしないでerrorをdelegateしようということ。

/// ```rust
/// # use std::error::Error;
/// #
/// # fn main() -> Result<(), Box<dyn Error>> {
/// your;
/// example?;
/// code;
/// #
/// #     Ok(())
/// # }
/// ```

上記のように、#を書くとcargo testでcompileされるが、user-visibleな箇所には現れないのでこの機能を利用する。

Function docs include error, panic, and safety considerations (C-FAILURE)

Errorを返すなら、# Errors sectionで説明を加える。panicするなら、# Panicsで。unsafeの場合は、# Safetyでinvariantsについて説明する。

Link all the thingsということで、他の型へのlinkがかける。
linkにはいくつか書き方があり、RFC1946に詳しくのっていた。

Cargo.toml includes all common metadata (C-METADATA)

Cargo.toml[package]に記載すべきfieldについて。licenseは書いておかないとcargo publishできなかった気がします。

Release notes document all significant changes (C-RELNOTES)

Release notes = CHANGELOGという理解でよいのでしょうか。
CHANGELOGについては、keep a changelogのformatに従っていました。
Unreleased sectionに変更点をためて行って、releaseのたびにそれをrelease versionに変える方式がやりやすかったです。
Breaking changeも記載されるとおもうのですが、なにが破壊的変更かがきちんと定義されているのはRustらしいと思いました。
また、crates.ioへのpublishされたsourceにはgitのtagを付与することも書かれていました。

Predictability

予測可能性について。

Smart pointers do not add inherent methods (C-SMART-PTR)

Boxのようなsmart pointerの役割を果たす型にfn method(&self)のようなinherent methodsを定義しないようにする。
理由としては、Box<T>の値がある場合にboxed_x.method()という呼び出しがDerefTのmethodなのかBox側なのか紛らわしいから。

Conversions live on the most specific type involved (C-CONV-SPECIFIC)

型の間で変換を行う際、ある型のほうがより具体的(provide additional invariant)な場合がある。
例えば、str&[u8]に対してUTF-8として有効なbyte列であることを保証する。
このような場合、型変換処理は具体型、この例ではstr側に定義する。このほうが直感的であることに加えて、&[u8]の型変換処理methodが増え続けていくことを防止できる。

Functions with a clear receiver are methods (C-METHOD)

特定の型に関連したoperationはmethodにする。

// Prefer
impl Foo {
    pub fn frob(&self, w: widget) { /* ... */ }
}

// Over
pub fn frob(foo: &Foo, w: widget) { /* ... */ }

Methodはfunctionに比べて以下のメリットがある。

  • importする必要がなく、値だけで利用できる。
  • 呼び出し時にautoborrowingしてくれる。
  • Tでなにができるかがわかりやすい
  • selfであることでownershipの区別がよりわかりやすくなる
    • ここの理由はいまいちわかりませんでした。

個人的にmethodにするか、関数にするか結構悩みます。
具体的にはmethodの中で、structのfieldの一部しか利用しない場合、利用するfieldだけを引数にとる関数を定義したくなったりします。(structを分割することが示唆しているのかもしれませんが)

Functions do not take out-parameters (C-NO-OUT)

いまいちよく理解できなかったのですが、戻り値を入力として受け取って加工して返すみたいなfunctionは避けようということでしょうか。例にあげられているコードがいまいち何を伝えたいかわからずでした。
Preferのfooはどうして,Bar二つ返してるんでしょうか。

// Prefer
fn foo() -> (Bar, Bar)

// Over
fn foo(output: &mut Bar) -> Bar

例外としては、buffer等のcallerがあらかじめ確保しているdata構造に対する処理として、read系の例があげられていました。

fn read(&mut self, buf: &mut [u8]) -> io::Result<usize>

Operator overloads are unsurprising (C-OVERLOAD)

std::opsを実装すると、*|が使えるようになるが、この実装はMulとして機能するようにする。

Only smart pointers implement Deref and DerefMut (C-DEREF)

Derefで委譲をやるようにしたことあったのですがアンチパターンということでやめました。
Stack overflowでも、delegateambassadorの利用を推奨している回答がありました。

Constructors are static, inherent methods (C-CTOR)

型のconstructorについて。
基本は、T::newを実装する。可能ならDefaultも。
domainによっては、new以外も可能。例えば、File::openTcpStream::connect
constructorが複数ある場合には、_with_fooのようにする。
複雑ならbuilder patternを検討する。
ある既存の型の値からconstructする場合は、from_を検討する。from_From<T>の違いは、unsafeにできたり、追加の引数を定義できたりするところにある。

Flexibility

Functions expose intermediate results to avoid duplicate work (C-INTERMEDIATE)

処理の過程で生成された中間データが呼び出し側にとって有用になりうる場合があるならそれを返すようなAPIにする。
これだけだといまいちわかりませんが、具体例として

  • Vec::binary_search
    • 見つからなかった時はinsertに適したindexを返してくれる
  • String::from_utf8
    • 有効なUTF-8でなかった場合、そのoffsetとinputのownershipを戻してくれる
  • HashMap::insert
    • keyに対して既存の値があれば戻してくれる。recoverしようとした際にtableのlookupを予め行う必要がない。

ownershipとるような関数の場合、エラー時に値を返すようなAPIは参考になるなと思いました。

Caller decides where to copy and place data (C-CALLER_CONTROL)

fn foo(b: &Bar) {
   let b = b.clone(); 
}

のようにするなら最初からfoo(b: Bar)でownershipをとるようにする。こうすれば呼び出し側でcloneするかmoveするかを選択できるようになる。

Functions minimize assumptions about parameters by using generics (C-GENERIC)

引数に対する前提(assumption)が少ないほど、関数の再利用性があがる。
ので、fn foo(&[i64])のようにうけないで

fn foo<I: IntoIterator<Item= i64>>(iter: I)

のようにうけて、値のiterateにのみ依存していることを表現する。 ただし、genericsにもdisadvantageがあるのでそれは考慮する。(code size等)

Traits are object-safe if they may be useful as a trait object (C-OBJECT)

traitを設計する際は、genericsのboundとしてかtrait objectとして利用するのかを決めておく。
object safeなtraitにobject safeでないmethodを追加して、object safeを満たさなくならないように注意する必要があるということでしょうか。
trait safeでないmethodを追加する際は、whereSelf: Sizedを付与してtrait objectからはよべなくする選択肢もあるそうで、Iteratorではそうしているという例ものっていました。
SendSyncと同様にtest caseでobject safeのassertも書いておいた方がよさそうだなと思いました。

Type safety

Newtypes provide static distinctions (C-NEWTYPE)

単位やId(UserId,ItemId,...)等をi64Stringではなく専用の型をきって静的に区別する。

struct Miles(pub f64);
struct Kilometers(pub f64);

impl Miles {
    fn to_kilometers(self) -> Kilometers { /* ... */ }
}
impl Kilometers {
    fn to_miles(self) -> Miles { /* ... */ }
}

Arguments convery meaning through types, not bool or Option (C-CUSTOM-TYPE)

boolじゃなくて専用の型をきろう。boolに対してenumにするのがわかりやすいのは理解できるのですが、Optionも使わない方がよいものなのかなと思いました。
Optionはecosystemと親和性あるから変にwrapすると使いづらくなるのでは。

// Prefer
let w = Widget::new(Small, Round)

// Over
let w = Widget::new(true, false)

Types for a set of flags are bitflags, not enums (C-BITFLAG)

他言語やシステムとの互換性の観点や整数値でフラグのsetを管理したい場合はbitflagsを使おう。

use bitflags::bitflags;

bitflags! {
    struct Flags: u32 {
        const FLAG_A = 0b00000001;
        const FLAG_B = 0b00000010;
        const FLAG_C = 0b00000100;
    }
}

fn f(settings: Flags) {
    if settings.contains(Flags::FLAG_A) {
        println!("doing thing A");
    }
    if settings.contains(Flags::FLAG_B) {
        println!("doing thing B");
    }
    if settings.contains(Flags::FLAG_C) {
        println!("doing thing C");
    }
}

fn main() {
    f(Flags::FLAG_A | Flags::FLAG_C);
}

Builders enable construction of complex values (C-BUILDER)

Tのconstructが複雑になってきたときには、TBuilderを作ることを検討する。必ずしもBuilderとする必要はなく、stdのchild processに対するCommandUrlParseOptionsのようにdomainに適した名前があるならそれを利用する。
Builderのmethodのreceiverに&mut selfをとるか、selfをとるかでそれぞれトレードオフがある。
どちらを採用してもone lineは問題なくかけるが、ifを書こうとするとselfをとるアプローチは再代入させる形にする必要がある。

// Complex configuration
let mut task = TaskBuilder::new();
task = task.named("my_task_2"); // must re-assign to retain ownership
if reroute {
    task = task.stdout(mywriter);
}

Dependability

Functions validate their arguments (C-VALIDATE)

RustのApiは一般的にはrobustness principleに従っていない。
つまり、インプットに対してliberalな態度を取らない。代わりにRustでは実用的な範囲でインプットをvalidateする。
validationは以下の方法でなされる。(優先度の高い順)

Static enforcement

// Prefer
fn foo(a: Ascii) { }

// Over
fn foo(a: u8) { }

ここではAsciiu8のwrapperでasciiとして有効なbyteであることを保証している。
こうしたstatic enforcementはcostをboundaries(u8が最初にAsciiに変換される時)によせ、runtime時のcostを抑えてくれる。

Dynamic enforcement

実行時のvalidationの欠点としては

  • Runtime overhead
  • bugの発見が送れること
  • Result/Optionが導入されること(clientがハンドリングする必要がある)

また、production buildでのruntime costをさける手段としてdebug_assert!がある。
さらに、_unchecked版の処理を提供し、runtime costをopt outする選択肢を提供している場合もある。

Destructors never fail (C-DTOR-FAIL)

Destructorはpanic時も実行される。panic時のdestructorの失敗はprogramのabortにつながる。
destructorを失敗させるのではなく、clean teardownをcheckできる別のmethodを提供する。(closeなど)
もし、こうしたcloseが呼ばれなかった場合は、Dropの実装はエラーを無視するかloggingする。

Destructors that may block have alternatives (C-DTOR-BLOCK)

同様にdestructorはblockingする処理も実行してはならない。
ここでも、infallibleでnonblockingできるように別のmethodを提供する。

Debuggability

All public types implement Debug (C-DEBUG)

publicな型にはDebugを実装する。例外は稀。

Debug representation is never empty (C-DEBUG-NONEMPTY)

空の値を表現していても、[]なり""のなんらかの表示をおこなうべきで、空の出力をするべきでない。

Future proofing

Sealed traits protect against downstream implementations (C-SEALED)

crate内でのみ実装を提供したいtraitについては、ユーザ側でimplできないようにしておくことで、破壊的変更を避けつつtraitを変更できるように保てる。


/// This trait is sealed and cannot be implemented for types outside this crate.
pub trait TheTrait: private::Sealed {
    // Zero or more methods that the user is allowed to call.
    fn ...();

    // Zero or more private methods, not allowed for user to call.
    #[doc(hidden)]
    fn ...();
}

// Implement for some types.
impl TheTrait for usize {
    /* ... */
}

mod private {
    pub trait Sealed {}

    // Implement for those same types, but no others.
    impl Sealed for usize {}
}

ユーザはprivate::Sealedを実装できないので、結果的にTheTraitを実装できずboundariesのみで利用できる。
libで時々みかけて、最初は意図がわかっていなかったのですが、ユーザにtraitを実装されると、traitのsignatureの変更が破壊的変更になることをきらってのことだとわかりました。もっとはやくガイドラインを読んでおけばよかったです。

Structs have private fields (C-STRUCT-PRIVATE)

publicなfieldをもつことは強いcommitmentになる。ユーザは自由にそのfieldを操作できるのでvalidationやinvariantを維持することができなくなる。
publicなfieldはcompoundでpassiveなデータ構造に適している。(C spirit)

Newtypes encapsulate implementation details (C-NEWTYPE-HIDE)

以下のようなiterationに関するロジックを提供するmy_transformを考える

use std::iter::{Enumerate, Skip};

pub fn my_transform<I: Iterator>(input: I) -> Enumerate<Skip<I>> {
    input.skip(3).enumerate()
}

Enumerate<Skip<I>>をユーザにみせたくないので、Newtypeでwrapすると

use std::iter::{Enumerate, Skip};

pub struct MyTransformResult<I>(Enumerate<Skip<I>>);

impl<I: Iterator> Iterator for MyTransformResult<I> {
    type Item = (usize, I::Item);

    fn next(&mut self) -> Option<Self::Item> {
        self.0.next()
    }
}

pub fn my_transform<I: Iterator>(input: I) -> MyTransformResult<I> {
    MyTransformResult(input.skip(3).enumerate())
}

こうすることで、ユーザ側のcodeを壊すことなく実装を変更できるようになる。
my_transform<I: Iterator>(input: I) -> impl Iterator<Item =(usize, I::Item)>のようなsignatureも可能だが、impl Traitにはトレードオフがある。
例えば、DebugCloneを書けなかったりする。

Data structures do not duplicate derived trait bound (C-STRUCT-BOUNDS)

ちょっと理解があやしいですが、trait boundの設けたstruct定義にderiveを書かないということでしょうか。
trait boundを書かなくてもderiveはgenericsがtraitを実装しているかで制御されるし、deriveを追加する際にtrait boundを追加するとそれはbreaking changeになるからという理解です。

// Prefer this:
#[derive(Clone, Debug, PartialEq)]
struct Good<T> { /* ... */ }

// Over this:
#[derive(Clone, Debug, PartialEq)]
struct Bad<T: Clone + Debug + PartialEq> { /* ... */ }

Necessities

Public dependencies of a stable crate are stable (C-STABLE)

public dependenciesのcrateがstable(>=1.0.0)でなければそのcrateはstableにできない。
Fromの実装等、public dependenciesは以外なところに現れる。

Crate and its dependencies have a permissive licence (C-PERMISSIVE)

Rust projectで作られているsoftwareはMITApache 2.0のdual licenseになっている。
Rustのecosystemとの親和性を重視するなら同じようにしておく。
crateのdependenciesのlicenseはcrate自身に影響するので、permissively-licensed crateは一般的にpermissively-licensed crateのみを利用する。

まとめ

Rustのapi guidelineをざっと眺めてみました。
いろいろなcrateで共通していることが書かれていた印象です。
自分で書いたりコードレビューしたりする際に参照していきたいです。