When need to use `ManualReply `?

I was following the sample code of rust-profile,i was confused by using ManualReply.

I’ve checked the docs here:
query in ic_cdk_macros - Rust (docs.rs)

so,when need to use ManualReply?

Thank you ~~

1 Like

In the Rust abstraction over the system interface, functions take parameters and produce return values. In the system interface, functions are () -> (), take input by calling ic0.msg_arg_data_*, and produce output by calling ic0.reply. Sometimes these two models of the world conflict.

The function you have posted is one that ‘returns’ Profile in Candid-encoded form. The convenient abstraction would be to return the Profile struct from the function - except Profile contains owned non-Copy data. To return it by value would require cloning it, and there is no lifetime that it could be returned under by reference because it is obtained through thread_local!. The canister would like to not waste cycles with an allocation (allocations are cheap, but it’s an example for when your operation isn’t cheap).

But ownership and lifetimes are irrelevant to the underlying operation - it’s just getting serialized and passed to ic0.reply, which does not require ownership. If you could just ‘return’ your data that way, there are no lifetime problems, and you can pass it to the function by reference just fine. Thus, the manual_reply = true flag on the export macros allows you to reply yourself to skip the entire problem. (This is necessary because otherwise the export macro would call ic0.reply itself, and calling ic0.reply twice traps.)

ManualReply is an abstraction over manually calling ic0.reply. It is a struct that contains no data - it only exists to provide CandidType::ty, so that declaring it as a return type allows you to still auto-generate Candid bindings. When you construct it with parameters, it immediately calls ic0.reply regardless of whether it’s returned or not. Saying

return ManualReply::one(Some(p));

is equivalent to saying

call::reply((Some(p),));
return;

except that declaring it as your return type ensures you cannot accidentally return a value without actually exiting the function. Use of this type is not required when using manual_reply = true - you can use ic_cdk::api::call::reply if you want to.

6 Likes

I hadn’t seen Adam’s reply but came across this motivating example and quickly understood why it’s needed:

2 Likes

Very good example, just that I had a problem after looking closely at the comments:

  1. Why do you say cdk 0.5 makes this unnecessary?
  2. Why are headers: HashMap<String, String> not headers: Vec<(String, String)>
  1. I think that’s referring to ic::cdk_setup, call::arg_data, and call::reply.

    I think I only added the macro, changed the type signature to include ManualReply and return value to ManualReply::one.

  2. I think because their Candid representation is the same.

    Related:

    Can you please support the data structure required to respond to http requests? · Issue #328 · dfinity/cdk-rs · GitHub

1 Like