https://phaazon.net/blog/on-owning-borrowing-pub-interface

ownership, borrowing, API

Disclaimer: this blog is about Rust and some of its intrinsics semantics, along with software design and architecture – especially for public interfaces like APIs. However, you may find it interesting to apply the concepts to any language that would support those ideas directly or indirectly.

I’ve been writing on a few examples code lately to add to documentations of some crates of mine. I write a lot of code that creates new objects that need other objects in order to be built. Most of the APIs you can see around tend to love the borrow principle – and I do. The idea is simple:

Depending on the crate you look at, the authors and how they see things, you might find a lot of ways to pass that string to your constructor. Let’s get technical. Especially, I want this blog post to give people a hint at how they should shape their APIs by driving the types with semantics.

The base code

struct Person {
  ident: String
}

Very simple to get started. We have a Person type that carries the name of that person. We don’t want to expose the internals of the type because we might add a lot of other things in there and allowing people to pattern-match a Person would break their code when we add more things.

impl Person {
  pub fn new(name: String) -> Person {
    Person { name }
  }
}

That works. This constructor works by move semantics: it expects you to move a String in in order to copy it into the Person value. Move semantics are clear here: the client must allocate a String or move one already allocated by someone else. This can get a bit boring. Imagine:

let someone = Person::new("Patrick Bateman"); // this won’t compile

Person::new("Patrick Bateman") doesn’t typecheck because "Patrick Bateman" has type &'static str here. It’s not a String.

Drive the allocation from within your code

So how can we fix the code above to have it compile?

let someone = Person::new("Patrick Bateman".to_owned()); // okay now

"Patrick Bateman".to_owned() makes a use of ToOwned::to_owned to clone the string. You could also have used Into::into. However, we’re still requiring clients of our API to perform the allocation – or move in, which is not ideal. A typical solution to this is to use a borrow and clone when needing an owned version.

impl Person {
  pub fn new(name: &str) -> Person {
    Person {
      name: name.to_owned()
    }
  }
}