There’s a lot to building a good #API. It has to communicate the underlying abstraction well. It has to include the right calls. It has to exclude the “wrong” calls. It has to take inputs and provide outputs in ways that are safe, convenient, and ergonomic. It has to communicate any transfers of ownership. It has to communicate lifetimes. It has to make it clear when some things must happen in a given sequence, and make it easy to call into it that way. It has to separate itself from implementation details. It has to fail in understandable ways at places where the failures can be handled. It has to have good names to back up everything I’ve said so far. It has to have good and up-to-date documentation. It has to have tests that don’t look inside the black box. This is important stuff, because it’s at the APIs that bugs happen: where two different pieces of code rub up against each other. Everything above is true in any language. If you’re writing a library, this is job one.
No language makes all of this easy. Some languages don’t even make all of this possible. #Cpp can do it. #RustLang can do it. Some others I know less well. It takes experience, skill, taste, and domain knowledge to do this. I am loving Rust because part of this job is required by the compiler. It’s still a lot of work; and you still have to do it.