rust-unofficial / patterns Goto Github PK
View Code? Open in Web Editor NEWA catalogue of Rust design patterns, anti-patterns and idioms
Home Page: https://rust-unofficial.github.io/patterns/
License: Mozilla Public License 2.0
A catalogue of Rust design patterns, anti-patterns and idioms
Home Page: https://rust-unofficial.github.io/patterns/
License: Mozilla Public License 2.0
Before finding the mem-replace idiom here, I implemented something like the following working alternative for a_to_b, which also doesn't (surprisingly to me at first) require MyEnum to be Copy.
Requires re-assignment on the calling side but avoids needing mem::replace or cloning the name. At where I am in learning rust, I'm probably not yet qualified, but would it be helpful to include this as an alternative and listing pros/cons or limitations?
Thanks for compiling these patterns!
#[derive(Debug, PartialEq)]
pub enum MyEnum {
A { name: String, x: u8 },
B { name: String }
}
pub fn a_to_b(e: MyEnum) -> MyEnum {
if let MyEnum::A { name, x: 0 } = e {
MyEnum::B { name }
} else {
e
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let mut e = MyEnum::A { name: "foo".to_owned(), x: 0 };
e = a_to_b(e);
assert_eq!(MyEnum::B { name: "foo".to_owned() }, e);
e = a_to_b(e);
assert_eq!(MyEnum::B { name: "foo".to_owned() }, e);
}
}
Could you just copy the first three paragraphs from your recent blog post?
In my opinion, it would be usueful to include at least one concrete example of each pattern at the end of the file. It shouldn't be anything complicated, just something that gives the "feel" of the practical usage.
The format!
idiom notes:
It is possible to build up strings using the push and push_str methods on a mutable String, or using its + operator. However, it is often more convenient to use format!, especially where there is a mix of literal and non-literal strings.
Using write!
on a String
may not be quite as terse as the latter or as efficient as the former, but it's a pretty good (and generally more readable) alternative. It does require having std::fmt::Write
in scope.
This is a common idiom, where using an &str
is often more flexible than using a &String
.
Borrowing lifetimes always becomes tricky whenever there's a container-node relationship. We can always use indexes to reference the nodes or allow the nodes to reference each other.
It would be nice to have something in CI that tells us if markdown is correct.
For example this markdown is not valid:
# section 1
### section 2
Or stuff like: avoid tabs or detect trailing whitespaces.
mem::replace(name, String::new())
can be made more terse by using
mem::take(name)
instead. Rust playground link to show it still compiles: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6f29fd3165760bf068a7c4200fa8cbc5
I'm a little wiped to write a full PR, and wanted to see if people agree here or not. We make extensive use of ZSTs in Diesel, which has sometimes led to the use of PhantomData
, even if the type we're wrapping with it is also zero sized.
As time has gone on though, I've realized that any use of PhantomData
is ultimately a sign that how you're going about implementing your hierarchy might be impossible to make object safe, which is a good thing to retain when you can. It's hard enough changing all of the fn foo<T: Foo>(foo: T)
to fn foo(foo: &Foo)
, adding additional work on yourself is painful.
As such, I've ultimately come to the conclusion that PhantomData
is a smell (maybe not an anti-pattern, but there's generally little value in distinguishing between the two)
If you create a collection type, avoid allocating until the first element is inserted. This has the nice property of being able to avoid allocation altogether if the collection stays empty (which, as it turns out, in the lifetime of the average program many do).
Hi @lambda-fairy,
you stated here that this repo needs some help, how do you imagine helping with this repository?
MarcoIeni & me would like to help. We could start reviewing all past issues and merging/closing or give some feedback to all pending PRs.
Long-term, we would also like to turn this repo into a rust book
that can be served with GitHub pages to the community.
Cheers,
Simonsan & MarcoIeni
I'm taking my inspiration from Ruby for this one.
Avoid use of nested conditionals for flow of control. Prefer a guard clause when you can assert invalid data. A guard clause is a conditional statement at the top of a function that bails out as soon as it can. - Ruby Style Guide @ No Nested Conditionals
First I'll provide three examples of the wrong way to do it. These are overly nested and trying to follow the paths or guess with else belongs to what can be confusing at a glance. These examples are a simple file config parse
fn read_config1() -> Result<Config, Error> {
let file = File::open("program.cfg");
if let Ok(f) = file {
let mut buf_reader = BufReader::new(f);
let mut contents = String::new();
if buf_reader.read_to_string(&mut contents).is_ok() {
let mut data: Vec<u8> = vec![];
for item in contents.
split("\n").
map(|s| s.to_string()).
filter(|s| !s.is_empty()).
collect::<Vec<String>>() {
let num = item.parse::<u8>();
if let Ok(conf) = num {
data.push(conf);
} else {
return Err(Error::ConfigParseFail);
}
}
Ok( Config { data: data } )
} else {
Err(Error::ConfigLoadFail)
}
} else {
Err(Error::ConfigLoadFail)
}
}
fn read_config2() -> Result<Config, Error> {
let file = File::open("program.cfg");
match file {
Ok(f) => {
let mut buf_reader = BufReader::new(f);
let mut contents = String::new();
match buf_reader.read_to_string(&mut contents) {
Ok(_) => {
let mut data: Vec<u8> = vec![];
for item in contents.
split("\n").
map(|s| s.to_string()).
filter(|s| !s.is_empty()).
collect::<Vec<String>>() {
let num = item.parse::<u8>();
match num {
Ok(conf) => data.push(conf),
_ => { return Err(Error::ConfigParseFail); },
}
}
Ok( Config { data: data } )
},
_ => { Err(Error::ConfigLoadFail) }
}
},
_ => { Err(Error::ConfigLoadFail) }
}
}
fn read_config3() -> Result<Config, Error> {
let file = File::open("program.cfg");
if let Ok(f) = file {
let mut buf_reader = BufReader::new(f);
let mut contents = String::new();
if buf_reader.read_to_string(&mut contents).is_ok() {
let mut data: Vec<u8> = vec![];
for item in contents.
split("\n").
map(|s| s.to_string()).
filter(|s| !s.is_empty()).
collect::<Vec<String>>() {
let num = item.parse::<u8>();
if let Ok(conf) = num {
data.push(conf);
} else {
return Err(Error::ConfigParseFail);
}
}
return Ok( Config { data: data } );
}
}
Err(Error::ConfigLoadFail)
}
And here is the correct usage of a Guard Clause which allows us to avoid deeply nested logic.
fn read_config4() -> Result<Config, Error> {
let file = File::open("program.cfg");
// Correct use of Guard Clause
if let Err(_) = file { return Err(Error::ConfigLoadFail); }
let f = file.unwrap();
let mut buf_reader = BufReader::new(f);
let mut contents = String::new();
// Correct use of Guard Clause
if let Err(_) = buf_reader.read_to_string(&mut contents) {
return Err(Error::ConfigLoadFail);
}
let mut data: Vec<u8> = vec![];
for item in contents.
split("\n").
map(|s| s.to_string()).
filter(|s| !s.is_empty()).
collect::<Vec<String>>() {
let num = item.parse::<u8>();
match num {
Ok(conf) => data.push(conf),
Err(_) => { return Err(Error::ConfigParseFail); }
}
}
Ok( Config { data: data } )
}
If you'd like to test the examples above I have a working gist here: danielpclark/guard_clause_rfc.rs … you just need to create the config file with the contents of "1\n2\n3\n"
.
Having your code implemented with guard clauses adds a huge benefit for code clarity and avoids having your conditionals carry your code too far and deep to the right.
Improvements for Guard Clause is currently in Pre-RFC as I would like to avoid using unwrap()
after a Guard Clause.
The guard clause doesn't have to be strictly for an Error
alternate return type. I believe it can apply for any time when there are two conditional paths and one of those paths is nothing but a return type.
Is this possible with rustfmt and mdbook?
In the short term, add issues for each pattern.
I have a local branch here that does this. Obviously some examples are not meant to run (e.g. partial copies from std
), those can be marked with ignore
.
Otherwise examples should run if it doesn't make them unreadable. I consider adding one or two lines of use
declarations acceptable.
Thoughts?
Could we get an example of how to implement the Observer pattern, or an alternative solution if that pattern is not a good fit in Rust? Thanks.
Hello.
There's also the facade pattern:
http://aturon.github.io/blog/2017/07/26/revisiting-rusts-modules/#what-is-a-module-system-anyway
Thanks.
Just an idea, would "Guard" count as a pattern?
Although sys::thread::scoped
is unstable, sys::sync::MutexGuard
is alive and well. Returning a guard object seems like an interesting way to make sure that an object can outlive past the curreny scope, by allowing the guard to be dropped and acting upon the referent (e.g. unlocking the Mutex
).
A fairly terse description: Returning a proxy type with an inner reference to the original, that incorporates #[must_use]
attribute and a probably reference to poison::Guard
. Returning the guard can allow referents to outlive the calling scope and yet can still be acted upon when the scope changes.
(Apologies if this is already written up or doesn't count! Maybe it's too narrow if it's only used for implementing concurrency primitives?)
The Fold pattern seemed confusing to me. The term "fold" in functional programming has a pretty clear definition (which includes a combining function), and it's not immediately clear from the example how it applies.
As used in the Rust compiler, it looks like their "Folders" are a general purpose map, reduce, or unfold operation on a tree, with the default operation of the base trait working as identity functions. Perhaps this is similar to Haskell's Traversable type class?
Anyways, I think it could be a little clearer. Perhaps in type theory or compiler circles using "Fold" in this context makes sense, but to me it seemed odd.
BTW, if you are curious, the term "fold" in a functional language sense was probably started by David Turner in the SASL language in the late 70's/early 80's.
I claim that the builder pattern is more or less an anti-pattern and that you should use the Default
trait instead. Here's why:
Let's say we have a struct:
pub struct Window {
pub title: &'static str,
pub width: usize,
pub height: usize,
}
Creator:
// Builder pattern
pub struct WindowBuilder {
__title: &'static str,
__width: usize,
__height: usize,
}
impl WindowBuilder {
pub fn new() -> Self {
Self {
__title: "Default title",
__width: 800,
__title: 600,
}
}
pub fn with_title(self, title: &'static str) -> Self {
Self {
__title: title,
__width: self.width,
__title: self.height,
}
}
pub fn with_dimensions(self, width: usize, height: usize) -> Self {
Self {
__title: self.title,
__width: width,
__title: height,
}
}
pub fn build(self) -> Window {
Window {
title: self.title,
width: self.width,
height: self.height,
}
}
}
// Default pattern: much less code!
impl Default for Window {
fn default() -> Self {
Self {
title: "Default title",
width: 800,
height: 600,
}
}
}
See how much code we need to construct a window in comparison to the Default
trait?
User:
// Default pattern
let window = Window {
title: "Original title",
.. Default::default()
};
// Builder pattern: not a significant reduction of usage code!
let window = WindowBuilder::new()
.with_title("Original title")
.build();
let window = WindowBuilder::new()
.with_title("Original title")
.with_dimensions(800, 600)
.with_title("Oops, overwritten title!")
.build();
The Default
trait protects against that, because you can't initialize the same field twice. The builder pattern simply overwrites the field and you don't get any warning.
Default
trait eliminates the need for the SomethingBuilder
struct. The SomethingBuilder
struct is an intermediate struct that provides a certain kind of type safety so that you have to call SomethingBuilder.build()
to construct a Something
out of a SomethingBuilder
. All of this is unnecessary if you use the Default
trait - less code with essentially the same outcome. The SomethingBuilder
has one appropriate use, in my opinion: When you need something to happen in the .build()
function and it needs to happen once (although you can implement this in a default()
function, too). For example, you need to tell the OS to create a window. This is where it's appropriate to use a builder. However, I've seen the builder pattern to be completely overused, which is why I'm writing this.Often times when I'm having a struct with many fields that can have default values, it is easier to implement a default trait than to write ten or twenty builder functions. And that is why I claim that the builder pattern is actually an anti-pattern and that you should use the Default
trait instead, wherever possible. It should at least be included somewhere in this repository.
The example seems unrealistic because it uses if let
instead of match
. The example should use match
since it is more general. Most of the cases where I would use this pattern would require the use of match
.
If you have a type Foo that has some complex initialization that you don't really want to inline into each example for every method on Foo, you can instead do:
/// bar is an important method on Foo. It does things.
/// #Example
/// ```
/// # fn call_bar(foo: &Foo) {
/// let value = foo.bar();
/// # }
/// ```
fn bar(&self) -> u32 {
//...
}
This avoids having to type several lines of logic to initialize foo
in a concise way without having to jump to doing a "```ignore".
The deny warnings page is a little out of date and should be updated to remove warnings that are no longer relevant.
Maybe there is something I'm missing because the lack of Rust syntax/concepts understanding (I'm still learning, sorry) but the goal of the Visitor pattern is not only to "allow multiple different algorithms to be written over the same data". The main reason when visitor pattern is used is to allow dynamic double dispatch. Say you have a pair of abstract Visitor and Data and you don't know at compile time what the actual type of Data and Visitor is, you can just say data.accept(visitor) and let the magic happen.
Just look at https://en.wikipedia.org/wiki/Visitor_pattern#C.2B.2B_example
This is perfectly doable in any OO language.
Refering to the example, how can I have a
let d : Data = some of(Stmt, Name, Expr, ...);
let v : Visitor = some of(Interpreter, ...);
and say d.accept(v) without doing match on types anywhere.
Can this be done in Rust?
Hi @nrc
It appears that there hasn't been much activity for this effort recently. I think this kind of information is quite valuable, and we should pass the torch to a new maintainer. I would gladly take responsibility for maintaining this project -- recently I was working on what I wanted to call the Rust Cookbook, and this seems to be right up the same ally. Also, I'm sure there are others who are perfectly capable and may be willing; @lfairy comes to mind.
Let me know!
I believe it may be useful to have some discussion about Small<T>
optimizations; ie SmallVec
, SmallHashMap
, etc. This thought started from a users.rust-lang post.
What do you all think?
Through my work with clippy, I've probably had more exposure to the negative consequences than others, so I'm going to write up something in the next few days.
The sample code in idioms dtor-finally cannot capture outside variable, which means it's useless in most cases
I recently started collecting some API design patterns here. I'm not sure if these match what this repo is all about (or which ones you already cover), but I just wanted to say: Feel free to take anything you like from that post! :)
(The markdown source of that post is here and I hereby relicense the content as MPL2 (in addition to CC-BY) and allow you to use it in this repo.)
In Idiom 5, the link is outdated.
TODO trait to separate visibility of methods from visibility of data (https://github.com/sfackler/rust-postgres/blob/master/src/lib.rs#L1400)
Collection of design patterns and the corresponding issues if not contained in the repository already (in reference to Design patterns (by Gamma, Helm, Johnson, Vlissides)). Some patterns are probably not applicable to/realizable with Rust as they stem from OOP, but we'll sort that out on the way.
But in general I think it is also nice to have design patterns
listed somewhere on a page that are not applicable to Rust and stated why and with an added workaround if any.
Checkmark = already existing in repository (link to file)
No checkmark = Link to corresponding issue
No checkmark and no link = Check if applicable for Rust
libstd
, and how it integrated into the Rusts source code, lessons can be learned about ergonomic project management and API design. Closely assosciated with platform-specific sub-modulesError
traits and Result
forwardingUnwrap()
ing every Result
instead of forwarding itunwrap
to Result
Requesting a design pattern for functions that take lots of parameters. For example this way:
fn foo(parameter_0: type, parameter_1: type, parameter_2: type, parameter_3: type, parameter_4: type, parameter_5: type, parameter_6: type) -> type {
// function body
}
is a way that I think should be an anti-pattern because it's too wide. I prefer things to be less than 80 chars wide.
I came across this conversation which describes the zipper pattern which might be of interest.
It could make sense to structure/sort some of the patterns into a subfolder named after corresponding categories as mentioned in Design patterns (by Gamma, Helm, Johnson, Vlissides)
.
patterns/structural
patterns/behavioural
patterns/creational
Patterns not fitting into any of the categories could stay in patterns
. When we merged all the older PRs we could start with that process to avoid merge conflicts.
Thoughts?
I'm thinking these patterns would be good candidates for inclusion in StackOverflow Documentation. What do you think?
I don't have a good name for this, but it's a pattern that Servo uses to sever linear dependencies between crates.
Before:
Crate A
pub struct FluxCapacitor;
impl FluxCapacitor {
pub fn new() -> FluxCapacitor {
FluxCapacitor
}
}
Crate B
extern crate a;
pub fn instantiate_flux_capacitor() {
let capacitor = a::FluxCapacitor::new();
//...
}
Crate C
extern crate b;
fn start() {
b::instantiate_flux_capacitor();
}
After:
Crate A_Shared
pub trait FluxCapacitorCreator {
fn create() -> Self;
}
Crate A
extern crate a_shared;
pub struct FluxCapacitor;
impl FluxCapacitor {
fn new() -> FluxCapacitor {
FluxCapacitor
}
}
impl FluxCapacitorCreator for FluxCapacitor {
fn create() -> FluxCapacitor {
FluxCapacitor::new()
}
}
Crate B
extern crate a_shared;
pub fn instantiate_flux_capacitor<T: a_shared::FluxCapacitorCreator>() {
let capacitor = T::create();
//...
}
Crate C
extern crate a;
extern crate b;
fn start() {
b::instantiate_flux_capacitor::<FluxCapacitor>();
}
The .collect()
instance for Result
and Option
is pretty useful, but is often overlooked by newcomers. I'd like to see an article that describes it.
I've got a personal project which does something pretty similar to this. It's essentially a git repository full of markdown pages.
What I did is use something called mdbook which will take all the markdown files and turn them into structured html. I then added a travis.yml
which will rebuild the html every time I commit some work and then automatically push to the ghpages branch.
I was wondering if you wanted to do something similar to make it easier for people to read and navigate all the useful documents here. It shouldn't require much effort, you just put all the *.md
source files in a src
directory and write a short SUMMARY.md
file to tell mdbook where everything goes.
here's my SUMMARY.md
:
# Summary
- [Using this Document](./using-the-wiki.md)
- [JavaScript](./JavaScript/javascript.md)
- [Docker](./docker-images.md)
- [Python](./Python/index.md)
- [SqlAlchemy](./Python/sqlalchemy.md)
- [Basic Server Setup](./basic-server-setup.md)
- [Rust](./Rust/index.md)
- [Using Rust outside Rust](./Rust/rust_interop.md)
- [Emails and Templates](./Rust/emails_and_templates.md)
- [Testing In C](./testing_in_c.md)
- [LaTeX Snippets](./latex.md)
And the generated documents are viewable here.
I'm used to use Self
for the class type inside impl
from:
// A Rust vector, see liballoc/vec.rs
pub struct Vec<T> {
buf: RawVec<T>,
len: usize,
}
impl<T> Vec<T> {
// Constructs a new, empty `Vec<T>`.
// Note this is a static method - no self.
// This constructor doesn't take any arguments, but some might in order to
// properly initialise an object
pub fn new() -> Vec<T> {
// Create a new Vec with fields properly initialised.
Vec {
// Note that here we are calling RawVec's constructor.
buf: RawVec::new(),
len: 0,
}
}
}
to:
// A Rust vector, see liballoc/vec.rs
pub struct Vec<T> {
buf: RawVec<T>,
len: usize,
}
impl<T> Vec<T> {
// Constructs a new, empty `Vec<T>`.
// Note this is a static method - no self.
// This constructor doesn't take any arguments, but some might in order to
// properly initialise an object
pub fn new() -> Self {
// Create a new Vec with fields properly initialised.
Self {
// Note that here we are calling RawVec's constructor.
buf: RawVec::new(),
len: 0,
}
}
}
this makes it easier in case of renaming etc., what do you think?
When dealing with unsafe
code, the containing module has to ensure safety by upholding invariants. To make this feasible, the module should be as small as possible, only containing the unsafe functionality and the code necessary to uphold the guarantees, and embedding this in a larger module that can safely use the abstraction.
This keeps the code needed for an unsafe audit in manageable size.
Looks like we have to main source for lint attributes and warnings/errors:
rustc -W help
In neither of these resources there's coverage for which of this these lint rules can get triggered depending on a compiler version, and which ones don't.
For example, unsafe-code
cannot suddenly get triggered by a compiler update.
But, missing-docs
, on the other hand, can get triggered whenever compiler supports docs for new language items.
I think not distinguishing these two types of lints is a common cause for denying/forbidding risky lints on the code.
Maybe there could be a Lint Group for these? Or some other way to tell them apart?
Suggest changing finish()
to use a self
receiver instead of &self
.
I sometimes see this in code from iterative-minded folks, they iterate over something, collecting the result in a Vec
. Then they iterate over the Vec
, producing yet another Vec
.
In most cases, the intermediate storage can be avoided. However, there are two exceptions:
start()
ing a number of threads one intends to join()
later, the intermediate storage is needed to allow the threads to run concurrently.I think this is a fairly uncontroversial example of an anti-pattern, but I don't think many people realise that std::sync::Once
even exists.
I think it could be nice to add a small contribution guide
to the repository stating what to take care about
SUMMARY.md
for the book generationallow edits of maintainers
so we can actually work together with people on thingstemplate
tbc
Hi,
Thanks for sharing. This will be more readable if you can create a gitbook with this :)
A suggested snappier name for compose-structs:
Borrow Decomposition.
In essence, you're decomposing what gets borrowed by the borrow checker (at least until it gets smarter with Chalk).
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.