Inversion of control in rust

Ah, good to know... Alloc will be required anyways... So maybe I'll fall back to that if I can't find a better solution. But Point3 is still something I'd really like

I appreciate your openness to feedback, and hope this is also well received. Note I don't really understand the example, but mine is mostly a point on readability anyway:

let service = provider
        .get::<ioc_rs::Transient<&dyn interface::Service>>()
        .expect("Expected plugin to register a &dyn Service");

Is this is an example of the IOC in action (snippet from the examples you mentioned)?

If so , I think I'd prefer the builder:

let service = Provider::builder().build::<interface::Service>().expect("…");

// or
let service = Provider::builder().service::<Service>().expect("…");

// or:
let service: Service<&dyn interface::Service> = Provider::builder().service().expect("…");

Generally, as I've learned rust, I've been surprised at just how well type inference works to minimize code and enhance readability with the builder pattern. I mean its much better than Java which makes you spell it all out.

Edit: You'll see above I totally missed the Transient part of your original. But I hope my point is clear enough in any case.

The benefit is that you don't have to recompile your runtime/platform if you need a new implementation of &dyn Service. It doesn't know plugin at all (and neither does plugin know runtime). You can e.g. use that to switch the Inference-Engine from TensorFlow to OpenVino without compiling any new code... Just replace the opencv.dylib with openvino.dylib and you're good... Try to run the example on your system: if you're on linux/windows, you might need to change the dylib-extension to .dll or .so

Maybe I should find an example which isn't pointing to ::get::<Singleton<i32>>() but instead ::get::<SingletonServices<i32>>(). This returns an iterator over all registered services. You can use this to collect routes from multiple dll's (again, no recompillation needed). This is also useful if you sell your software in components... You can simply extend new functionality by putting a new .dylib into your plugin-folder and restart the application

PS: I can't see a single location where I have to specify more types than required...

1 Like

Thanks, I'll give that a try. Yes, perhaps my immediate reaction is just from old wounds from the Java sense of the IoC acronym. In other words, my feedback so far might just be more in terms of marketing semantics. But as you should know, in open source, sadly, marketing is often the linch pin to adoption!

This is why I asked you for a better name :wink:

1 Like

Can you expand on this? Data behind an Arc will only "leak" if there's some long-lived clone of the Arc; once the reference count hits zero it's deallocated, and there's no mechanism to prolong it beyond that (afaik). And of course as long as you stick to safe code you never have to worry about use-after-free :‌)

1 Like

I suspect that the idea is that destroying the ServiceProvider should also destroy the services it provided, and the compiler should reject any code that would require them to live beyond that time.

1 Like

Ah, okay -- could that be achieved by having the services hold Weak and the provider keep the only Arc? It's not the same kind of compile-time check but it might be almost as good.

Exactly

As far as I understand it, Services are still able to upgrade the Weak to an actual Arc... And it would be very unergonomic to work with upgrade all over the place...

1 Like

Only if Provider holding an Arc is still here. If the last Arc is dropped, trying to upgrade Weak will fail.

Agree... Unfortunately, I assume that library users will just call unwrap on every upgrade and if there is an error, it will fail at runtime and not compile time... I'd like to avoid that if possible

You could stick a wrapper around Weak that hides the short-lived upgrade, exposing the resulting reference only to a closure: playground. That prevents services from getting their own Arcs. But there is admittedly an ergonomic cost (which I think is worth paying if the alternative is unsoundness).

1 Like

This might be outside the box, right field, but if 99% of use cases are setup as start of program and live forever (i.e. no runtime reloading, etc.) then maybe you just mem::forget the registrants or use lazy static for 'static lifetimes?

A wrapper is what I just proposed on the explicit thread about this problem, as this thread was more about code review :slight_smile:. I totally agree that unsound code has no place in a public api. I however prefer a wrapper which simply implements an AsRef, as real programs often have no fallback if a crucial service is missing, which leads to long Error-Handling simply to tell the programmer that he messed up...

Maybe I'll introduce a use-after-free hook if a Service is still in use when killing the provider... The library user can then decide to panic (during development) or e.g. log an error in production...

1 Like

Oops, sorry I missed your post there

This is currently true, but will change. Scopes are a crucial concept of IoC and will be implemented by having a new ServiceProvider with an upstream fallback to this "Application IoC" you describe right now. This is very important for Services which are doing e.g. Authorization, because they require information about the current user

1 Like

Okay, sure; so maybe you want to at least make sure the API is pleasant for the common case, though.

I think there's a really interesting opportunity for Rust here -- tying scoped into the lifetime system could be a compile-time check that would keep people from ever saving a request-scoped object into a shared-scoped cache, or similar.

(I used to hit that a bunch in a project that used hibernate -- thankfully we've deprecated it now, though.)

2 Likes

Inversion of Control is a dead wrong anti-pattern and self-defeating concept.

This might not be an interesting sub-thread for everyone, but for my part, I just realized what it was about the Java exposure to IoC that left a bitter taist in my mouth (and tracks up my arm):

At its height and with Java's Spring framework in particular, IoC effectively became a drug and all code, whether it actually benefited from it in any way/or not, had to be structured to use it. The result was an already boiler-plate heavy language becoming so verbose that it was almost impossible to isolate the non-boiler-plate, real logic. It was as if we were drug-addicts that got payed by the Line of Code (LoC), and spent all our money getting high!

If you can just avoid that, then you have my blessing (FWIW) to add this to Rust!

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.