The biggest different is whether they break the code flow and propagate up or not. This is a potential source of snowballing complexity as the throwable exception interface can potentially keep growing with each module layer. On the other hand, the propagation property is helpful for using the aggregate exception handling technique.
Another major difference is the code flow structure required by try/catch blocks vs error return values. As the chapter points out, try/catch scopes mean that you either need to bundle your error handling to reduce the try/catch blocks, or you need a ton of boilerplate wrapping each potential throwable function call to handle each call's error types. They also create new scopes which makes it harder to use immutable variable declarations.
Error return values don't alter the code flow, but they do require you to handle the error type to narrow it down to a success type. This means you can't aggregate error handling or just let it crash, but it does allow you to handle the errors in cleaner ways like with guard statements.
You can of course convert between thrown and returned errors, but converting from a thrown to returned error requires a lot more verbosity and disrupts your programs flow much more than adding an error guard. On the otherhand, now you're forcing the module caller to handle the error. Returned values do have the benefit of easily being converted into a thrown error if you decide that you want to handle it by aborting the operation and sending it upstream.
As a module author that puts you in a position where you have to decide what error interface you're going to impose on the caller. One way I've been thinking of to decide between the two is whether or not the error is an expected part of the module's operation. For example, I think it makes sense for something like a file permission error to be part of a returned error object instead of being a thrown exception. It's unlikely that a caller would want to purposefully ignore those kinds of errors that are an expected part of a module's execution.
Returned errors also imply that they're errors that are safe to recover from and that they haven't left the system in an unstable state. For an error to be returned, that means that a return statement was definitely encountered, which means that the procedure completed in some kind of intentional code flow. A thrown error could have happened anywhere in the code flow of a module you called so it's unclear if the module was exited in an intentional way.
On the otherhand, I don't think it's good to default to returning every type of error either. Since a returned error implies that it's safe to recover, if you start returning every error, you lose the utility of a returned error and you greatly increase the cognitive load of the interface by exposing the caller to a ton of error types. For example, if you have a catch statement that has a catch all handler that wraps it in a returned value, how could the caller of your module possibly recover from an error that you couldn't explicitly handle yourself? If you're doing that, you're basically exposing the caller to an unknown unknown and removing the utility of a returned error.
The chapter does briefly mention returning special errors values, but doesn't follow up on them. Normally it doesn't matter too much to the point being made, but many of the points around error handling in the chapter are specifically impacted by the code flow characteristics of thrown errors, so I think there's some interesting alternatives behaviors and observations to explore around returned errors as well.
Returned errors don't solve all the problems with thrown errors and have a tradeoff of their own deficiencies as well, but I think a mix of the two could help improve the signal between how to handle error cases, with returned errors being used for more formal error interfaces that should be recovered and thrown errors for more informal error interfaces that should be handled at job level aggregate handlers. Of course on the other hand, mixing the two could also simply make things more confusing for callers instead if it's done poorly.