alercah: If bar fails to uphold its invariants, there are two cases
If bar is trusted, this is a failure of bar
If bar is not trusted, this is a failure of foo
alercah
right
eternaleye
Either way, the UB is in foo, not in bar
alercah
so an unsafe operation is one that warns "there are some invariants which must be maintained or else I may cause UB", as opposed a safe one which promises no UB
eternaleye
Trusted isn't about "I don't cause UB" - it's about "You can gate your own UB on my invariants"
alercah
right
code using an unsafe operation must prove that it does not cause UB; for this purpose it is permitted to rely on invariants of trusted functions
vadimcn has quit
currently, the compiler requires that safe fns must explicitly state that they have discharged the obligation with unsafe {}
unsafe fns are not required to do so
this seems to me like if we don't allow any inference, it's merely an opinionated decision
there is no inherent reason that safe fns require unsafe {}, or that unsafe fns do not
if unsafe is *specifically* about UB, then there is no reason to write an fn function that doesn't in turn rely on unsafe behaviour
unsafe *could* be reused by safe Rust code for maintaining internal invariants, rather than just UB invariants
in that case, unsafe fns that did not rely on any further unsafe behaviour would make sense
requiring unsafe {} inside unsafe fns would explicitly demarcating where the code is maintaining proof obligations
*would be
varkor joined the channel
I don't think this would be worthless, because it narrows the scope of where those proof obligations lie
but it's not any more necessary than unsafe{} on safe fns
*in
the other possibility that comes to mind is the idea of a function which promises certain invariants *provided* other invariants hold
is trusted sufficient for this? in isolation, yes, because a trusted function can make its invariants conditional
and unsafe code calling it must ensure it calls it correctly in order to be able to rely on those invariants
in its proof
rkruppe
i don't think there's any principled/philosophical reason for `unsafe fn` to be automatically able to perform operations that need `unsafe {}` otherwise. it's just common for unsafe fns to do _some_ unsafe operations inside, that is all. and this "convenience" has downsides. it doesn't take a whole philosophy of unsafe code and trusted code to argue that IMO
alercah
well I want to think about propagation first before I say that I agree
trusted I think propagates fine; conditionally-trusted generic code says "If the provided callees promise to uphold their invariants, then I promise to uphold mine."
the proof obligation lies on the trusted fn here
what about unsafe propagation?
is it meaningful to have conditionally-unsafe code?
rkruppe
for me the reason to want the unsafe block in `unsafe fn() { unsafe { foo() } }` to be required is *much* simpler: some unsafe fns need to be unsafe to call (e.g., target features) but don't necessarily need to do unsafe stuff themselves, so it should be possible to write those without accidentially doing unsafe things in the body. end of story.
alercah
actually hm, does it make sense for trusted code to prpagate
a conditionally-trusted fn is one that is trusted only if all its conditioned-on callees are trusted
in other words, it only promises its guarantees if all relevant callees do too
so the dual of that would be a conditionally-unsafe fn is one that is unsafe if any of its conditioned-on callees are unsafe
rkruppe
i still don't know what exactly you mean by "trusted" (tbh that might be because i've only half-followed the discussion earlier) and for the reasons i described it's not really relevant to my position on the relation of `unsafe fn` and unsafe blocks
no offense but everything in that thread i've read is philosophizing that hurts my head and doesn't illuminate anything to me
alercah
rkruppe: ah ok
basically the problem that gives rise to trusted is that unsafe code can't actually verify that, say, Eq is actually an equivalence relation
and so it can't rely on it
rkruppe
isn't that what `unsafe trait` is for?
alercah
yeah
'trusted' is trying to generalize that notion
so a conditionally-unsafe fn says "if any of my callees require invariants to maintain, then I will only promise to maintain them if these other invariants are maintained"
fabiim joined the channel
rkruppe
huh?
an unsafe fn requires invariants to be upheld to be called, it doesn't promise to _uphold_ invariants
(unless you call "not UB" an invariant, which is an abuse of terminology in multiple ways IMO)
so a "conditionally-unsafe" fn would be some that... sometimes requires invariants to be uphold by the caller? but that's just a more complex invariant period.
alercah
by conditional here, I mean generically
rkruppe: conditionally-unsafe code is promising that if all of its generic callees are themselves safe, it will be safe. Otherwise, it will be unsafe; that is, it will have some invariant whose violation will lead to UB.
rkruppe
OK that i can make sense of
alercah
it still has a proof obligation to discharge, therefore, when calling a potentially-unsafe callee
it has a moral obligation to document the invariants it requires when it is unsafe
and a proof obligation that, if called with those invariants satisfied, it will not violate any invariants of its callers
rkruppe
i still don't see how that can actually be very useful, though. the invariants that need to be upheld not only depends on the callees but also on everything else the function does that might be relevant to the callees. therefore it's very difficult and anti-modular to state what a concrete invocation requires (and thus, what the caller needs to ensure)
lukaramu has quit
alercah
yeah, I'm not sure it's super useful
I can make contrived examples but I'm not sure they're useful in general
since mostly they become roughly equivalent to imposing additional invariants on the callee
and it's easy to express "this passed-in closure must obey these restrictions" in documentation
expressing it in-language, and not doing so by using trusted, seems very edge casey
so then the last thing is masking
unsafe { } is the masking of unsafety, which discharges proof obligations
trusted { } would be masking untrustedness, which *creates* proof obligations
in other words, it is saying that some part of the function has proof obligations which the callees---preceding code in the function---must uphold
but that's worthless; the function has access to its own code
without some way of actually checking invariants, you could do the same with a comment like "at this point, the list must be sorted, so retrieving the first element retrieves the least element in the list"
trusted { } would just be syntactic bloviation there, I think
this is again true even in conditionally-trusted functions
so while trusted and unsafe are dual concepts, I don't think either really lines up with effects
I think there is advantage to always require unsafe { } to discharge proof obligations, even in unsafe code
and no advantage to allowing trusted { } to crate them
*create
you can model them as effects, but I simply think it doesn't quite line up in either case
eternaleye: ^
finished my rubberducking
niconii joined the channel
Muhannad joined the channel
eternaleye
alercah: Sure it does make sense to have conditionally unsafe code. `map` with an unsafe fn as its transformation is a classic example.