Not the solution you're looking for but I personally like having a wrapper and doing something like this (let's pretend this is for Twilio)
trait TwilioClient {
send_sms(num: &str, text: &str)
}
struct TwilioClientHttp {
client: HttpClient
}
impl TwilioClient for TwilioClientHttp {...}
struct TwilioClientFake {}
impl TwilioClient for TwilioClientFake {...}
And that's nice because now we have that abstraction layer away from IO. This will feel like overkill in some cases for sure, but I do find it great for a lot of cases. You can even just make that TwilioClientFake inside of the testing area itself directly. Go has done a good job pushing the idea of accepting interfaces everywhere, but it's ad hoc construction of those is a bit leaner.
Sorry for the StackOverflow-esque non-answer though. Also excuse my Rust, it's a bit rusty.