Unexpected Printf Behavior in Go WASM - Nothing Prints
While working on boot.dev’s Go Playground, I came across a very strange error. The standard library’s fmt.Printf() function prints nothing to the console when called. Nothing.
For those of you who are familiar with the function, when compiled to a “normal” executable fmt.Printf prints a formatted string to standard output. As per the official documentation, this program:
package main
import (
"fmt"
)
func main() {
const name, age = "Kim", 22
fmt.Printf("%s is %d years old.", name, age)
}
Will print:
Kim is 22 years old.
The interesting thing is that when the same exact program is compiled using Web Assembly, we get a different result. If you want to try it, copy the above program and run it here.
Spoiler alert: It doesn’t print anything.
However, if you change the program slightly:
package main
import (
"fmt"
)
func main() {
const name, age = "Kim", 22
// add a newline character
fmt.Printf("%s is %d years old.\n", name, age)
}
Then it may print the expected:
Kim is 22 years old.
thought if you run the two programs back to back, you may actually find that this is printed instead:
Kim is 22 years old.Kim is 22 years old.
Why?
When compiled to Web Assembly, the fmt.Printf function is writing to a buffer, and that buffer is not cleared until a newline character is printed to standard out. In other words, you can call fmt.Printf as many times as you want, but nothing is printed until a \n character comes through standard output.
Take a look at the writeSync() code in Go’s wasm_exec.js, which is a required dependency to execute Go Web Assembly in the browser:
writeSync(fd, buf) {
outputBuf += decoder.decode(buf);
const nl = outputBuf.lastIndexOf("\n");
if (nl != -1) {
console.log(outputBuf.substr(0, nl));
outputBuf = outputBuf.substr(nl + 1);
}
return buf.length;
}
As you can see, console.log() is only called on the buffer if a newline is found within the output string, otherwise, the output is just appended to the stateful outputBuf.
My current working theory is that it was implemented this way because there is no way to print to the console in a browser without appending a new line. JavaScript’s console.log() always appends a new line.
Related Articles
Authenticate Users with "Sign In With Google" in Golang
Jul 22, 2020 by Lane Wagner - Boot.dev co-founder and backend engineer
Users love convenience. If your goal is to make it easy for users to register with your app or website, then implementing the “Sign in with Google” option should be at the top of your priority list. If you are like me, then you may find Google’s documentation on the subject to be lackluster at best, and downright confusing at worst. Here we will go step-by-step through the authentication process so you can implement Google sign-in easily.
Rust Backend vs Go Backend in Web Development
Jul 17, 2020 by Lane Wagner - Boot.dev co-founder and backend engineer
Rust and Go are two of the industry’s biggest successes when it comes to developing modern programming languages. Both languages compete in terms of backend web development, and it’s a fierce competition. Golang and Rust are new languages, have growing communities, and are fast and efficient. When it comes to microservice architectures, frameworks, and apps, Rust and Go are household names on the backend.
Running Go in the Browser With Web Assembly (WASM)
Jul 01, 2020 by Lane Wagner - Boot.dev co-founder and backend engineer
If you are familiar with the Go Playground, then you know how convenient it is to be able to have a Go scratchpad in the browser. Want to show someone a code snippet? Want to quickly test some syntax? Browser-based code pads are helpful. On that note, I created a new playground. The cool thing about this new playground that it doesn’t use a remote server to run code, just to compile it. The code runs in your browser using web assembly (WASM).
Make Maps and Slices in Golang - A Guide to Initialization
Jun 29, 2020 by Lane Wagner - Boot.dev co-founder and backend engineer
There are quite a few ways to create new maps and slices in Go, for example, they can both be initialized using the make() function, the new() function, as literals, or by using the var keyword. With so many choices, which option is best? Or perhaps better asked, which one is best in your situation? Let’s take a look.