Skip to content
Sven Nilsen edited this page Sep 21, 2015 · 2 revisions

What makes a robust library?

In Rust ecosystems, it is common to prefer small libraries that focuses on a particular problem.

The nature of every idea is that it tries to put itself in the center of the universe.

When designing libraries, one often have to consider the ideal thing from the perspective of the idea, and then make tradeoffs to integrate well with other libraries.

Everything comes at a cost: Lines of code, structs, traits, everything!

Dependency explosion

This a phenomena that when depending on a library of certain complexity, it tends to grow into multiple libraries over time. When there is need for new features in one dependency, it gets new dependencies. This leads to an "explosion" of dependencies that increases compile times and reduces the ratio of code that actually goes into the final product.

To avoid dependency explosion, one can use backend agnostic design and keep the common libraries small with few dependencies.

Abstractions on top of abstractions

The motivation to design abstractions is to reuse code that depends on the abstraction. There is no need to abstract over abstractions, unless they are different.

Abstractions are for interfacing reusable code with different implementations

When a library is only concerned with its own idea, it might enforce too much how other libraries should be written when depending on it. This leads to new abstractions to avoid the ideas of the libraries underneath, without adding significant new functionality.

To write a good abstraction, one need to consider two things:

  • There should be a way of working around the library if necessary
  • The primary goal of the abstraction should be focus on how it interfaces

Therefore, big ideas of design should be moved closer to the top of the dependency graph. It does not mean that you should avoid big ideas, but that they usually tend to be bad for interfacing.

People care about their problems

When designing APIs, it is a good idea to consider what people care about. Usually they just want to solve the problem they have.

For example:

  • They want to get something on the screen
  • They want to start implementing their ideas
  • They want the choice of libraries to integrate well with other libraries

There are many tradeoffs you can make by understanding the problem more closely. Sometimes we put a lot of effort into a design around an idea, and then redesign it to match more closely to how the library was used, reducing the idea a bit.

Traits are not the only way

Traits are excellent for implementing one idea in different ways, but it tends to not scale comprehensively as building blocks for new ideas.

  • Trait implementations can not take advantage of stack context
  • It is hard to keep track of how the program executes if it is split into many method calls
  • Iterators, callbacks and generics is how people usually interface with code
  • Traits are often for creating new reusable code

For example, a Widget is a good candidate for a trait, because it lets you reuse the implementation.

For example, a SendMessage is not a good candidate if it is only used once. It could be just a function with a callback instead.

Clone this wiki locally