The Beast HTTP library that just got accepted in to Boost follows this approach. E.g. the following Fields concept specifies the requirements the library has on its any HeaderMap-like type you feed it. Writing an adapter for your own types then becomes straightforward
http://vinniefalco.github.io/beast/beast/concepts/Fields.htm...
Really, you want a concept that's like a trait, but is explicit about the fact that there's exactly one implementation of the trait, and that that implementation is discoverable at compile time. Less like an interface, more like a C typedef in a third-party-library header file—just without the "header file" part.
One problem is construction. When you need to interact with a library at one remove using interfaces everywhere, you can't simply new up instances; you have to go via factories. And that leads to ugly alien code.
Another is extension (OpenGL). If your super duper implementation has nifty new features, how do you expose them if you're living behind an interface? Does the interface enable extension in a usable way? Or is it indirect and awkward again?
In practice, you code and test against one or two implementations (bug compatibility), and other implementations are only supported by accident or effort by the implementors.
(I'm fully on board with the idea that a language ecosystem is constrained by the highest level of abstraction in the type system that's shared across all ecosystem libraries. It's the strongest argument for a large standard library.)
That's mostly a Java problem, though, which lacks first-class types.
Why is a factory more alien than simply newing up an instance? Shouldn't they have basically the same interface? Is it just a matter of giving people rope to hang themselves... because they can do an arbitrary interface, someone inevitably will make a weird one, and then someone else will think it's cute, and the fashion gets perverted?
That does seem like a problem. It could be solved with culture, but solving problems with culture is hard so I understand your decision to write it off as a bad direction.
I am writing a lot of code with this kind of structure lately though, so you've got me a little worried. One of the things I dislike about a library is when it exposes raw data structures to the user, and then presumes other libraries will understand that structure. For example, if I have a client library that is sending HTTP requests, and then another separate library on the server that handles them, there is an opportunity there for miscommunication in that data layer.
On the other hand, if I have one library which gives me both a factory and an interface which consumes that factory object, then the library handles both sides of the data structure management. I am forced to only work through the interface, which means as long as my libs are in order, everything should be able to communicate.
Sorry if that's vague, but I'm programming in JavaScript and this post is about Rust and you're referencing Java and OpenGL, so I'm not sure exactly where the ground is. Maybe I only like my approach because it's JavaScript, so I can't ever assume the type of anything is correct. #stockholmsyndrome
Needing to use a factory means you don't have ambient authority to create instances. Code that constructs needs to be parameterized by the factory, irrespective of how far down a call chain it is. Annotating the call stack with a handle to the library adds clutter and clumsiness throughout.
You can kind of get around this using module systems of various kinds, and the de facto module system in JS of putting your entire program in a function parameterized by the modules it uses - it's far from the worst way of doing things, and it's a lot less clumsy than many alternatives. There's a bonus in that it's the typical idiom in JS. But it isn't in many other languages, so the benefits of the API design pattern needs to be traded off against how it clashes with the language culture. It's not an unalloyed good.
[1] https://www.collinsdictionary.com/dictionary/english/at-one-...
I really do need to give Rust a try though. I'd been holding back because my impressions of it was the core libraries were still in flux so code that compiled today might fail 2 months down the line. Is this still the case now?
It's true that some cases still use nightly, as some people want to opt into cutting-edge stuff. But it's much much less than it was a while back.
Now, I'm willing to use the bleeding edge, since I'm just just learning the language, but it still just bugs me in that way that code smells always do.
The way how interface{} works there is you actually write your own named structure that is bootstrapped by interface{} rather than dealing with interface{} as a generic type that needs casting. So what you're actually working with is an io.Reader / io.Writer structure but which can be transferred to any domain providing your specific implementation of Reader / Writer supports the same methods (since it's the methods that define the interface you avoid all the horrible hacks than normally trouble interface{}). This means you can transfer data from a gzip archive to a base64 encoder or JSON marshaller to a OS STD* file or network device all as if they were the same logical interface and without having to write a lot of additional layers encoding / decoding the data nor describing the interface. It all works surprisingly painlessly. In fact it's literally the only time when working with interface{} that the process isn't painful.
So it's going nothing to do with fanboyism nor stockholm syndrome, interface{} just behaves quite differently to it's usual behavior and works really well in this specific situation in my personal opinion. If the Go developers left interface{} there instead of using it as a hacky alternative to generics then I doubt there would be the same backlash against it. But sadly they didn't.
On the other hand, this is the Nth time that somebody implemented a HTTP library on some platform. It seems that something can be improved still, software-engineering wise.