[Advanced Rust] 2.5. API Design Principles of Flexibility Pt.1 - Contracts and More Flexible Interfaces with Generic Parameters
2.5.1. Code Contracts Your code, whether explicitly or implicitly, contains a contract. A contract has two sides: A contract is a requirement, which is a restriction on how the code is used A contract is a promise, which is a guarantee about how the code behaves When designing APIs, there is a useful rule of thumb: avoid imposing unnecessary restrictions, and only make promises you can keep . Why? Adding restrictions or removing promises requires a major semantic version change and may break other code When you first design an API, loosening restrictions and later adding extra promises is usually backward-compatible 2.5.2. Restrictions and Promises Common forms of restrictions in Rust are: Trait bounds Argument types Common forms of promises are: Trait implementations Return types Some Examples Let's look at an API evolving through three versions: fn frobnicate ( s : String ) -> String The first version takes a String and returns a String Its contract is that the caller performs allocation (because both the parameter and return value are owned, allocation is inevitable), and its promise is that it returns an owned String The problem with this function is that, without changing the signature, it cannot later be turned into a “no-allocation” function, because both the argument and return value are owned fn frobnicate ( s : & str ) -> Cow < '_ , str > The second version relaxes the contract a bit Its contract is that it accepts only a string reference, and its promise is that it returns either a string reference or an owned String , namely the Cow type This version is still somewhat rigid. For example, the argument is &str ; if I pass in a String , I still have to convert it first. Also, because the return value is Cow , it cannot return string-owning types other than String and &str (for example, OsString ) fn frobnicate < T : AsRef < str >> ( s : T ) -> T The third version relaxes the contract further Now both the parameter and the return value only require a type th