noir-lang / noir

Noir is a domain specific language for zero knowledge proofs
https://noir-lang.org
Apache License 2.0
824 stars 178 forks source link

Refactor and simplify string formatting #4238

Open vezenovm opened 5 months ago

vezenovm commented 5 months ago

Problem

Before this PR https://github.com/noir-lang/noir/pull/4101 the fmtstr type is only resolved when trying to print the type using a Brillig foreign call. Now with resolve_assert_message we also must handle formatting of strings as we are saving an assertion message during execution that is to be displayed upon an unsatisfied constrain. The new dynamic assertion message resolution also reuses the same methods println does for formatting.

Happy Case

We should have a uniform startegy for formatting strings. Ideally this would not be in the "backend" and done by foreign calls. Foreign calls should be only for requesting a service from the caller that is executing the program such as printing to stdout or saving a failure message. Right now the current foreign call builtins on nargo perform these functions but also format the string output.

A possible solution was posted here (https://github.com/noir-lang/noir/pull/4101#discussion_r1473423444). We should settle on the strategy for simplifying formatting in this discussion issue.

Alternatives Considered

We could lean into a format oracle that returns a string of variable length (this would have to be added to the language). println and resolve_assert_message would then have more isolated functionality and call format themselves.

A Display trait of some kind could be added and then it would be up to users to format their types in Noir (with the core team formatting the primitive and stdlib types). It would most likely only be available in an unconstrained environment. This would most likely require some kind of string builtins or a format oracle, so we would still have formatting in the "backend".

Additional Context

No response

Would you like to submit a PR for this Issue?

No

Support Needs

No response

jfecher commented 5 months ago

We could lean into a format oracle that returns a string of variable length

I don't think this would be possible to add as a type. It is similar to a string slice but if we return it from an oracle and its dependent on some other data's display method then we wouldn't know the length of the string at compile-time.

vezenovm commented 5 months ago

I don't think this would be possible to add as a type.

You are correct we couldn't return it from the unconstrained environment to the constrained environment, but I was thinking we could still have it as a type similar to slices. We would then have a format oracle in the stdlib, but not a public wrapper around it for users to access.

It would just be a way to make sure each foreign call has it owns separate functionality. println would end up looking something like this where the only thing print does is display a string rather than formatting and writing to stdout:

#[oracle(format)]
unconstrained fn format_oracle<T>(input: T) -> String {}

unconstrained pub fn println<T>(input: T) {
    let input = format_oracle(input);
    print_oracle(true, input);
}