Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions compiler/noirc_frontend/src/monomorphization/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -889,10 +889,11 @@ impl<'interner> Monomorphizer<'interner> {

if let ast::Expression::Ident(ident) = original_func.as_ref() {
if let Definition::Oracle(name) = &ident.definition {
if name.as_str() == "println" {
if name.as_str() == "print" {
// Oracle calls are required to be wrapped in an unconstrained function
// Thus, the only argument to the `println` oracle is expected to always be an ident
self.append_printable_type_info(&hir_arguments[0], &mut arguments);
// The first argument to the `print` oracle is a bool, indicating a newline to be inserted at the end of the input
// The second argument is expected to always be an ident
self.append_printable_type_info(&hir_arguments[1], &mut arguments);
}
}
}
Expand Down
29 changes: 22 additions & 7 deletions docs/docs/standard_library/logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ keywords:
[
noir logging,
println statement,
print statement,
debugging in noir,
noir std library,
logging tutorial,
Expand All @@ -17,14 +18,13 @@ keywords:
]
---

The standard library provides a familiar `println` statement you can use. Despite being a limited
implementation of rust's `println!` macro, this construct can be useful for debugging.
The standard library provides two familiar statements you can use: `println` and `print`. Despite being a limited implementation of rust's `println!` and `print!` macros, these constructs can be useful for debugging.

You can print the output of println statements in your Noir code by using the `nargo execute` command or the `--show-output` flag when using `nargo test` (provided there are println statements in your tests).
You can print the output of both statements in your Noir code by using the `nargo execute` command or the `--show-output` flag when using `nargo test` (provided there are print statements in your tests).

It is recommended to use `nargo execute` if you want to debug failing constrains with `println` statements. This is due to every input in a test being a constant rather than a witness, so we issue an error during compilation while we only print during execution (which comes after compilation). `println` will not work for failed constraints caught at compile time.
It is recommended to use `nargo execute` if you want to debug failing constrains with `println` or `print` statements. This is due to every input in a test being a constant rather than a witness, so we issue an error during compilation while we only print during execution (which comes after compilation). Neither `println`, nor `print` are callable for failed constraints caught at compile time.

The `println` statement is unconstrained, so it works for outputting integers, fields, strings, and even structs or expressions. For example:
Both `print` and `println` are generic functions which can work on integers, fields, strings, and even structs or expressions. Note however, that slices are currently unsupported. For example:

```rust
use dep::std;
Expand All @@ -40,10 +40,9 @@ fn main(age : Field, height : Field) {
std::println(age + height);
std::println("Hello world!");
}

```

You can print multiple different types in the same statement and string as well as a new "fmtstr" type. A `fmtstr` can be specified in the same way as a normal string it just should be prepended with an "f" character:
You can print different types in the same statement (including strings) with a type called `fmtstr`. It can be specified in the same way as a normal string, just prepended with an "f" character:

```rust
let fmt_str = f"i: {i}, j: {j}";
Expand All @@ -59,4 +58,20 @@ You can print multiple different types in the same statement and string as well

let foo = fooStruct { my_struct: s, foo: 15 };
std::println(f"s: {s}, foo: {foo}");

std::println(15); // prints 0x0f, implicit Field
std::println(-1 as u8); // prints 255
std::println(-1 as i8); // prints -1
```

Examples shown above are interchangeable between the two `print` statements:

```rust
let person = Person { age : age, height : height };

std::println(person);
std::print(person);

std::println("Hello world!"); // Prints with a newline at the end of the input
std::print("Hello world!"); // Prints the input and keeps cursor on the same line
```
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ keywords:

The string type is a fixed length value defined with `str<N>`.

You can use strings in `assert()` functions or print them with
`std::println()`. See more about [Logging](../../standard_library/logging).
You can use strings in `assert()` functions or print them with `std::println()`. See more about [Logging](../../standard_library/logging).

```rust
use dep::std;
Expand All @@ -42,7 +41,7 @@ fn main() {
}
```

## Escape characters
## Escape Characters

You can use escape characters for your strings:

Expand All @@ -58,6 +57,32 @@ You can use escape characters for your strings:
Example:

```rust
let s = "Hello \"world" // prints "Hello "world"
let s = "Hello \"world"; // prints "Hello "world"
let s = "hey \tyou"; // prints "hey you"
```

## Formatted Strings

You can prepend a string with the singular `f` token to create a formatted string. This is useful when logging, as it allows injection of local variables:

```rust
let var = 15;
std::println(f"var {var}") // prints "var 0x0F"

let var = -1 as u8;
std::println(f"var {var}") // prints "var 255"

let var : i8 = -1;
std::println(f"var {var}") // prints "var -1"

// prints "Hello
//world"
std::println(f"Hello
world");

std::println(f"hey \tyou"); // prints "hey \tyou"
```

A type can be specified to print numbers either as hex via `Field`, unsigned via `u*` types and signed via `i*` types.

Note that escaped characters in formatted strings `fmtstr` will be outputted as defined, i.e. "\n" will be printed `\n`, not as a new line. You can add a newline or other whitespace by creating a multiline string as in the example above.
12 changes: 8 additions & 4 deletions noir_stdlib/src/lib.nr
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@ mod option;
mod string;
mod test;
// Oracle calls are required to be wrapped in an unconstrained function
// Thus, the only argument to the `println` oracle is expected to always be an ident
#[oracle(println)]
unconstrained fn println_oracle<T>(_input: T) {}
// Thus, the only argument to the `println` oracle is expected to always be an ident
#[oracle(print)]
unconstrained fn print_oracle<T>(_with_newline: bool, _input: T) {}

unconstrained pub fn print<T>(input: T) {
print_oracle(false, input);
}

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

#[foreign(recursive_aggregation)]
Expand Down
12 changes: 10 additions & 2 deletions test_programs/execution_success/strings/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,26 @@ fn main(message: pub str<11>, y: Field, hex_as_string: str<4>, hex_as_field: Fie
let x = 10;
let z = x * 5;
std::println(10);
std::print(10);

std::println(z); // x * 5 in println not yet supported
std::print(z);
std::println(x);
std::print(x);

let array = [1, 2, 3, 5, 8];
assert(y == 5); // Change to y != 5 to see how the later print statements are not called
std::println(array);
std::print(array);

bad_message = "hell\0\"world";
std::println(bad_message);
std::print(bad_message);
assert(message != bad_message);

let hash = std::hash::pedersen_commitment([x]);
std::println(hash);
std::print(hash);

assert(hex_as_string == "0x41");
// assert(hex_as_string != 0x41); This will fail with a type mismatch between str[4] and Field
Expand All @@ -36,6 +42,10 @@ fn test_prints_strings() {

std::println(message);
std::println("goodbye world");

std::print(message);
std::print("\n");
std::print("goodbye world\n");
}

#[test]
Expand All @@ -52,8 +62,6 @@ fn test_prints_array() {
}

fn failed_constraint(hex_as_field: Field) {
// TODO(#2116): Note that `println` will not work if a failed constraint can be
// evaluated at compile time.
// When this method is called from a test method or with constant values
// a `Failed constraint` compile error will be caught before this `println`
// is executed as the input will be a constant.
Expand Down
23 changes: 14 additions & 9 deletions tooling/nargo/src/ops/foreign_calls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub trait ForeignCallExecutor {
/// This enumeration represents the Brillig foreign calls that are natively supported by nargo.
/// After resolution of a foreign call, nargo will restart execution of the ACVM
pub(crate) enum ForeignCall {
Println,
Print,
CreateMock,
SetMockParams,
SetMockReturns,
Expand All @@ -31,7 +31,7 @@ impl std::fmt::Display for ForeignCall {
impl ForeignCall {
pub(crate) fn name(&self) -> &'static str {
match self {
ForeignCall::Println => "println",
ForeignCall::Print => "print",
ForeignCall::CreateMock => "create_mock",
ForeignCall::SetMockParams => "set_mock_params",
ForeignCall::SetMockReturns => "set_mock_returns",
Expand All @@ -42,7 +42,7 @@ impl ForeignCall {

pub(crate) fn lookup(op_name: &str) -> Option<ForeignCall> {
match op_name {
"println" => Some(ForeignCall::Println),
"print" => Some(ForeignCall::Print),
"create_mock" => Some(ForeignCall::CreateMock),
"set_mock_params" => Some(ForeignCall::SetMockParams),
"set_mock_returns" => Some(ForeignCall::SetMockReturns),
Expand Down Expand Up @@ -92,7 +92,7 @@ pub struct DefaultForeignCallExecutor {
last_mock_id: usize,
/// The registered mocks
mocked_responses: Vec<MockedCall>,
/// Whether to print [`ForeignCall::Println`] output.
/// Whether to print [`ForeignCall::Print`] output.
show_output: bool,
}

Expand Down Expand Up @@ -120,9 +120,14 @@ impl DefaultForeignCallExecutor {
decode_string_value(&fields)
}

fn execute_println(foreign_call_inputs: &[ForeignCallParam]) -> Result<(), ForeignCallError> {
let display_values: PrintableValueDisplay = foreign_call_inputs.try_into()?;
println!("{display_values}");
fn execute_print(foreign_call_inputs: &[ForeignCallParam]) -> Result<(), ForeignCallError> {
let skip_newline = foreign_call_inputs[0].unwrap_value().is_zero();
let display_values: PrintableValueDisplay = foreign_call_inputs
.split_first()
.ok_or(ForeignCallError::MissingForeignCallInputs)?
.1
.try_into()?;
print!("{display_values}{}", if skip_newline { "" } else { "\n" });
Ok(())
}
}
Expand All @@ -134,9 +139,9 @@ impl ForeignCallExecutor for DefaultForeignCallExecutor {
) -> Result<ForeignCallResult, ForeignCallError> {
let foreign_call_name = foreign_call.function.as_str();
match ForeignCall::lookup(foreign_call_name) {
Some(ForeignCall::Println) => {
Some(ForeignCall::Print) => {
if self.show_output {
Self::execute_println(&foreign_call.inputs)?;
Self::execute_print(&foreign_call.inputs)?;
}
Ok(ForeignCallResult { values: vec![] })
}
Expand Down
1 change: 1 addition & 0 deletions tooling/nargo_fmt/tests/expected/print.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use dep::std;

fn main() {
std::print("Hello world");
std::println("Hello world");
}
3 changes: 2 additions & 1 deletion tooling/nargo_fmt/tests/expected/print2.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use dep::std;

fn main( ) {
fn main() {
std::print("Hello world");
std::println("Hello world");
}
5 changes: 4 additions & 1 deletion tooling/nargo_fmt/tests/input/print.nr
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
use dep::std;

fn main() { std::println("Hello world"); }
fn main() {
std::print("Hello world");
std::println("Hello world");
}
5 changes: 3 additions & 2 deletions tooling/nargo_fmt/tests/input/print2.nr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use dep::std;

fn main( ) {
std::println("Hello world");
fn main() {
std::print("Hello world");
std::println("Hello world");
}