There’s nothing really special about the “guard” function, it’s just a function being called before your primary endpoint’s function. So there’s no meaningful difference between those two approaches except how you invoke them, but calling your function manually gives you more flexibility.
Indeed in my real use case I wrote an assertion function instead of using a guard but, I was curious about it anways. As there is a caller() function, I was wondering if maybe there is another function that deserialize the args and can be use anywhere, that way it would have been possible to access the args in a guard. I guess it doesn’t exist then and it is not possible.
Actually there is. A guard function is the only way that the ‘reject’ functionality is accessed. A canister function must either reply, reject, or trap. The macros unconditionally reply at the end of the function, because forcing the user to call reply would be annoying, but this then prevents the user from calling reject, because the auto-inserted reply will then cause a trap. A guard function is the only way to reject without trapping (using the CDK macros).
And yes, to get the arguments, you can use arg_data. It’s the same function that is used to produce the arguments for the canister method itself - the real symbol exported to wasm is () -> ().
A guard function is the only way that the ‘reject’ functionality is accessed
I wasn’t aware of that aspect of the guard function, that’s good to know, thanks! Is there any advantage to rejecting over trapping?
And yes, to get the arguments, you can use arg_data
Calling arg_data would deserialize the arguments again though, right? Is it worth paying the extra cycles for deserialization to be able to reject instead of trapping?
That’s a good point @NathanosDev, thanks. In the particular feature I’m building, beside cycles, speed matters so from a design perspective might be good to not add an extra deserialize.
Using the manual_reply = true parameter in the update or query macros turns off the automatic reply, letting either a manual reply or a manual reject. So you can reject without trapping using the cdk macros. Be ware though that a reply or a reject must be called before the method returns.
For those curious, reject and trap are different. For a reject the canister still commits the state changes of the current message. For a trap the system rolls back the state changes of the current message.
Thanks! So when we’re talking about guard functions we can probably safely assume that they’re not going to alter state, so rejecting and trapping would essentially be the same if there’s no other differences.
I think that is an unsafe assumption, what if a canister puts a lock on the user in the guard function, or counts user calls for rate limiting or similar.