Please forgive this post for being a bit tangential. To me, this whole idea of implicit parameters bears a strong resemblance to 'algebraic effects' (Haskell) or 'monadic effects' (Scala) or 'abilities' (Unison). Though Unison is the most 'upstart' of these languages, I like its conception of this idea the best. I'll try to summarize Unison's take here, but to be honest I only have a shallow understanding.
Edit: I forgot to mention that the Unison creators attribute their implementation of effect handlers to ideas that came from the Frank language, which is explored in more clinical detail in the Frank language authors' paper titled Do Be Do Be Do (yes, actually).
Unison is a pure functional language, so they also run into the classic "but how to IO?" problem. Unison's solution repackages monadic effects into "abilities" and "ability handlers". Practically, this looks like a second set of parameters ("abilities") declared on functions and has some slightly different rules for how the parameters propagate up to definitions implicitly and how such functions compose. Abilities themselves are declared like interfaces/traits, but they have more access to the control flow during execution. Here's an expanded excerpt of Unison's docs on abilities:
Here's a simple pure function that doesn't use abilities / do any fancy IO:
// impl of `msg` concatenates the Text arg name to construct another Text value
msg name = "Hello there " ++ name ++ "!"
Its definition would be:
// `msg` is a function that takes a Text and returns a Text
msg : Text -> Text
This function uses msg
defined above to do some IO (print to stdout):
greet name = printLine (msg name)
It's definition would be:
// greet is a function that takes a Text and returns Unit (nothing), but also requires the IO ability (the `{IO}` part)
greet : Text ->{IO} ()
msg
could also be declared as msg : Text ->{} Text
which is identical to the previous definition except it explicitly declares that it accepts no abilities.
Abilities 'propagate up' definitions in a call chain. E.g. a function greetMs name = greet ("Ms " ++ name)
would inherit the IO ability in its definition implicitly despite not using IO directly: greetMs : Text ->{IO} ()
.
A function can require more than one ability, e.g. in someFn : Text ->{IO, Abort} ()
, someFn
is like greet
but also requires the Abort ability.
A function can "handle" an ability in a function that it calls, which enables it to avoid requiring that ability in callers.
(Imagine I put an example here that calls `greet` with a handler for IO that puts calls to printLine
in a buffer and returns the buffer, and thus its definition doesn't have to include `{IO}` in its list
of abilities.)
(sorry I don't know how to do that, and I suspect it would get a bit too far into the weeds for this post)
Now I think that Abilities are pretty neat and seems to be related to the "Implicit Parameters" being discussed here, but I have no idea how they would relate to a real Rust implementation and I'm not making any specific recommendations, I just wanted to connect this thing to that other thing just in case we can learn something from other work in this space.
Here are (probably too many) resources I found that discuss abilities in Unison:
Video content: