From dc86ac912a2bdb3c22b00f1b8e5f356a54d1628e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 24 Oct 2018 02:29:53 -0700 Subject: [PATCH 1/5] [beta] ci: Move global credentials to web configuration This commit moves a number of our encrypted credentials stored in configuration files in this repository to env vars on the web UI. This will hopefully make it easier to rotate credentials in the future as well as quickly change them if the need arises. (quicker than landing a PR that is). This also updates the travis deployment process to always use the `aws` command line tool which we're already installing on Linux and should enable us to avoid all `dpl` gem issues as well as have greater control over what's going where. --- .travis.yml | 110 ++++++++++----------------------------------------- appveyor.yml | 19 ++------- 2 files changed, 24 insertions(+), 105 deletions(-) diff --git a/.travis.yml b/.travis.yml index ec8060b9f56aa..990cac4dbe98b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -197,23 +197,10 @@ matrix: . src/ci/docker/x86_64-gnu-tools/repo.sh; commit_toolstate_change "$MESSAGE_FILE" "$TRAVIS_BUILD_DIR/src/tools/publish_toolstate.py" "$(git rev-parse HEAD)" "$(git log --format=%s -n1 HEAD)" "$MESSAGE_FILE" "$TOOLSTATE_REPO_ACCESS_TOKEN"; -env: - global: - - SCCACHE_BUCKET=rust-lang-ci-sccache2 - - SCCACHE_REGION=us-west-1 - - AWS_ACCESS_KEY_ID=AKIAJAMV3QAMMA6AXHFQ - # AWS_SECRET_ACCESS_KEY=... - - secure: "j96XxTVOSUf4s4r4htIxn/fvIa5DWbMgLqWl7r8z2QfgUwscmkMXAwXuFNc7s7bGTpV/+CgDiMFFM6BAFLGKutytIF6oA02s9b+usQYnM0th7YQ2AIgm9GtMTJCJp4AoyfFmh8F2faUICBZlfVLUJ34udHEe35vOklix+0k4WDo=" - # TOOLSTATE_REPO_ACCESS_TOKEN=... - - secure: "ESfcXqv4N2VMhqi2iIyw6da9VrsA78I4iR1asouCaq4hzTTrkB4WNRrfURy6xg72gQ4nMhtRJbB0/2jmc9Cu1+g2CzXtyiL223aJ5CKrXdcvbitopQSDfp07dMWm+UED+hNFEanpErKAeU/6FM3A+J+60PMk8MCF1h9tqNRISJw=" - before_install: - # We'll use the AWS cli to download/upload cached docker layers, so install - # that here. - - if [ "$TRAVIS_OS_NAME" = linux ]; then - pip install --user awscli; - export PATH=$PATH:$HOME/.local/bin; - fi + # We'll use the AWS cli to download/upload cached docker layers as well as + # push our deployments, so download that here. + - pip install --user awscli; export PATH=$PATH:$HOME/.local/bin - mkdir -p $HOME/rustsrc # FIXME(#46924): these two commands are required to enable IPv6, # they shouldn't exist, please revert once more official solutions appeared. @@ -276,6 +263,23 @@ after_success: echo "#### Build successful; Disk usage after running script:"; df -h; du . | sort -nr | head -n100 + - > + if [ "$DEPLOY$DEPLOY_ALT" == "1" ]; then + mkdir -p deploy/$TRAVIS_COMMIT; + if [ "$TRAVIS_OS_NAME" == "osx" ]; then + rm -rf build/dist/doc && + cp -r build/dist/* deploy/$TRAVIS_COMMIT; + else + rm -rf obj/build/dist/doc && + cp -r obj/build/dist/* deploy/$TRAVIS_COMMIT; + fi; + ls -la deploy/$TRAVIS_COMMIT; + deploy_dir=rustc-builds; + if [ "$DEPLOY_ALT" == "1" ]; then + deploy_dir=rustc-builds-alt; + fi; + travis_retry aws s3 cp --no-progress --recursive --acl public-read ./deploy s3://rust-lang-ci2/$deploy_dir + fi after_failure: - > @@ -322,77 +326,3 @@ after_failure: notifications: email: false - -before_deploy: - - mkdir -p deploy/$TRAVIS_COMMIT - - > - if [ "$TRAVIS_OS_NAME" == "osx" ]; then - rm -rf build/dist/doc && - cp -r build/dist/* deploy/$TRAVIS_COMMIT; - else - rm -rf obj/build/dist/doc && - cp -r obj/build/dist/* deploy/$TRAVIS_COMMIT; - fi - - ls -la deploy/$TRAVIS_COMMIT - -deploy: - - provider: s3 - bucket: rust-lang-ci2 - skip_cleanup: true - local_dir: deploy - upload_dir: rustc-builds - acl: public_read - region: us-west-1 - access_key_id: AKIAJVBODR3IA4O72THQ - secret_access_key: - secure: "kUGd3t7JcVWFESgIlzvsM8viZgCA9Encs3creW0xLJaLSeI1iVjlJK4h/2/nO6y224AFrh/GUfsNr4/4AlxPuYb8OU5oC5Lv+Ff2JiRDYtuNpyQSKAQp+bRYytWMtrmhja91h118Mbm90cUfcLPwkdiINgJNTXhPKg5Cqu3VYn0=" - on: - branch: auto - condition: $DEPLOY = 1 - - # this is the same as the above deployment provider except that it uploads to - # a slightly different directory and has a different trigger - - provider: s3 - bucket: rust-lang-ci2 - skip_cleanup: true - local_dir: deploy - upload_dir: rustc-builds-alt - acl: public_read - region: us-west-1 - access_key_id: AKIAJVBODR3IA4O72THQ - secret_access_key: - secure: "kUGd3t7JcVWFESgIlzvsM8viZgCA9Encs3creW0xLJaLSeI1iVjlJK4h/2/nO6y224AFrh/GUfsNr4/4AlxPuYb8OU5oC5Lv+Ff2JiRDYtuNpyQSKAQp+bRYytWMtrmhja91h118Mbm90cUfcLPwkdiINgJNTXhPKg5Cqu3VYn0=" - on: - branch: auto - condition: $DEPLOY_ALT = 1 - - # These two providers are the same as the two above, except deploy on the - # try branch. Travis does not appear to provide a way to use "or" in these - # conditions. - - provider: s3 - bucket: rust-lang-ci2 - skip_cleanup: true - local_dir: deploy - upload_dir: rustc-builds - acl: public_read - region: us-west-1 - access_key_id: AKIAJVBODR3IA4O72THQ - secret_access_key: - secure: "kUGd3t7JcVWFESgIlzvsM8viZgCA9Encs3creW0xLJaLSeI1iVjlJK4h/2/nO6y224AFrh/GUfsNr4/4AlxPuYb8OU5oC5Lv+Ff2JiRDYtuNpyQSKAQp+bRYytWMtrmhja91h118Mbm90cUfcLPwkdiINgJNTXhPKg5Cqu3VYn0=" - on: - branch: try - condition: $DEPLOY = 1 - - - provider: s3 - bucket: rust-lang-ci2 - skip_cleanup: true - local_dir: deploy - upload_dir: rustc-builds-alt - acl: public_read - region: us-west-1 - access_key_id: AKIAJVBODR3IA4O72THQ - secret_access_key: - secure: "kUGd3t7JcVWFESgIlzvsM8viZgCA9Encs3creW0xLJaLSeI1iVjlJK4h/2/nO6y224AFrh/GUfsNr4/4AlxPuYb8OU5oC5Lv+Ff2JiRDYtuNpyQSKAQp+bRYytWMtrmhja91h118Mbm90cUfcLPwkdiINgJNTXhPKg5Cqu3VYn0=" - on: - branch: try - condition: $DEPLOY_ALT = 1 diff --git a/appveyor.yml b/appveyor.yml index 04951454c29e1..d519993f14288 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,12 +1,5 @@ environment: - SCCACHE_BUCKET: rust-lang-ci-sccache2 - SCCACHE_REGION: us-west-1 - AWS_ACCESS_KEY_ID: AKIAJAMV3QAMMA6AXHFQ - AWS_SECRET_ACCESS_KEY: - secure: 7Y+JiquYedOAgnUU26uL0DPzrxmTtR+qIwG6rNKSuWDffqU3vVZxbGXim9QpTO80 SCCACHE_DIGEST: f808afabb4a4eb1d7112bcb3fa6be03b61e93412890c88e177c667eb37f46353d7ec294e559b16f9f4b5e894f2185fe7670a0df15fd064889ecbd80f0c34166c - TOOLSTATE_REPO_ACCESS_TOKEN: - secure: gKGlVktr7iuqCoYSxHxDE9ltLOKU0nYDEuQxvWbNxUIW7ri5ppn8L06jQzN0GGzN # By default schannel checks revocation of certificates unlike some other SSL # backends, but we've historically had problems on CI where a revocation @@ -235,10 +228,8 @@ before_deploy: deploy: - provider: S3 - skip_cleanup: true - access_key_id: AKIAJVBODR3IA4O72THQ - secret_access_key: - secure: tQWIE+DJHjXaV4np/3YeETkEmXngtIuIgAO/LYKQaUshGLgN8cBCFGG3cHx5lKLt + access_key_id: $(AWS_ACCESS_KEY_ID) + secret_access_key: $(AWS_SECRET_ACCESS_KEY) bucket: rust-lang-ci2 set_public: true region: us-west-1 @@ -252,10 +243,8 @@ deploy: # This provider is the same as the one above except that it has a slightly # different upload directory and a slightly different trigger - provider: S3 - skip_cleanup: true - access_key_id: AKIAJVBODR3IA4O72THQ - secret_access_key: - secure: tQWIE+DJHjXaV4np/3YeETkEmXngtIuIgAO/LYKQaUshGLgN8cBCFGG3cHx5lKLt + access_key_id: $(AWS_ACCESS_KEY_ID) + secret_access_key: $(AWS_SECRET_ACCESS_KEY) bucket: rust-lang-ci2 set_public: true region: us-west-1 From a12b62e9ef1dd24013ec7b1e9952de8fa557a349 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Thu, 25 Oct 2018 19:44:32 +0000 Subject: [PATCH 2/5] Fixes #46775 -- don't mutate the process's environment in Command::exec Instead, pass the environment to execvpe, so the kernel can apply it directly to the new process. This avoids a use-after-free in the case where exec'ing the new process fails for any reason, as well as a race condition if there are other threads alive during the exec. --- src/libstd/sys/unix/process/process_common.rs | 8 ++ src/libstd/sys/unix/process/process_unix.rs | 99 +++++++++++++++++-- src/test/run-pass/command-exec.rs | 12 +++ 3 files changed, 111 insertions(+), 8 deletions(-) diff --git a/src/libstd/sys/unix/process/process_common.rs b/src/libstd/sys/unix/process/process_common.rs index 77f125f3c5b56..3d5920dfb69ac 100644 --- a/src/libstd/sys/unix/process/process_common.rs +++ b/src/libstd/sys/unix/process/process_common.rs @@ -141,6 +141,10 @@ impl Command { pub fn get_argv(&self) -> &Vec<*const c_char> { &self.argv.0 } + #[cfg(not(target_os = "fuchsia"))] + pub fn get_program(&self) -> &CString { + return &self.program; + } #[allow(dead_code)] pub fn get_cwd(&self) -> &Option { @@ -244,6 +248,10 @@ impl CStringArray { pub fn as_ptr(&self) -> *const *const c_char { self.ptrs.as_ptr() } + #[cfg(not(target_os = "fuchsia"))] + pub fn get_items(&self) -> &[CString] { + return &self.items; + } } fn construct_envp(env: BTreeMap, saw_nul: &mut bool) -> CStringArray { diff --git a/src/libstd/sys/unix/process/process_unix.rs b/src/libstd/sys/unix/process/process_unix.rs index 7f1f9353c6d09..f41bd2c20720a 100644 --- a/src/libstd/sys/unix/process/process_unix.rs +++ b/src/libstd/sys/unix/process/process_unix.rs @@ -8,6 +8,8 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use env; +use ffi::CString; use io::{self, Error, ErrorKind}; use libc::{self, c_int, gid_t, pid_t, uid_t}; use ptr; @@ -39,13 +41,15 @@ impl Command { return Ok((ret, ours)) } + let possible_paths = self.compute_possible_paths(envp.as_ref()); + let (input, output) = sys::pipe::anon_pipe()?; let pid = unsafe { match cvt(libc::fork())? { 0 => { drop(input); - let err = self.do_exec(theirs, envp.as_ref()); + let err = self.do_exec(theirs, envp.as_ref(), possible_paths); let errno = err.raw_os_error().unwrap_or(libc::EINVAL) as u32; let bytes = [ (errno >> 24) as u8, @@ -113,12 +117,48 @@ impl Command { "nul byte found in provided data") } + let possible_paths = self.compute_possible_paths(envp.as_ref()); match self.setup_io(default, true) { - Ok((_, theirs)) => unsafe { self.do_exec(theirs, envp.as_ref()) }, + Ok((_, theirs)) => unsafe { self.do_exec(theirs, envp.as_ref(), possible_paths) }, Err(e) => e, } } + fn compute_possible_paths(&self, maybe_envp: Option<&CStringArray>) -> Option> { + let program = self.get_program().as_bytes(); + if program.contains(&b'/') { + return None; + } + // Outside the match so we can borrow it for the lifetime of the function. + let parent_path = env::var("PATH").ok(); + let paths = match maybe_envp { + Some(envp) => { + match envp.get_items().iter().find(|var| var.as_bytes().starts_with(b"PATH=")) { + Some(p) => &p.as_bytes()[5..], + None => return None, + } + }, + // maybe_envp is None if the process isn't changing the parent's env at all. + None => { + match parent_path.as_ref() { + Some(p) => p.as_bytes(), + None => return None, + } + }, + }; + + let mut possible_paths = vec![]; + for path in paths.split(|p| *p == b':') { + let mut binary_path = Vec::with_capacity(program.len() + path.len() + 1); + binary_path.extend_from_slice(path); + binary_path.push(b'/'); + binary_path.extend_from_slice(program); + let c_binary_path = CString::new(binary_path).unwrap(); + possible_paths.push(c_binary_path); + } + return Some(possible_paths); + } + // And at this point we've reached a special time in the life of the // child. The child must now be considered hamstrung and unable to // do anything other than syscalls really. Consider the following @@ -152,7 +192,8 @@ impl Command { unsafe fn do_exec( &mut self, stdio: ChildPipes, - maybe_envp: Option<&CStringArray> + maybe_envp: Option<&CStringArray>, + maybe_possible_paths: Option>, ) -> io::Error { use sys::{self, cvt_r}; @@ -193,9 +234,6 @@ impl Command { if let Some(ref cwd) = *self.get_cwd() { t!(cvt(libc::chdir(cwd.as_ptr()))); } - if let Some(envp) = maybe_envp { - *sys::os::environ() = envp.as_ptr(); - } // emscripten has no signal support. #[cfg(not(any(target_os = "emscripten")))] @@ -231,8 +269,53 @@ impl Command { t!(callback()); } - libc::execvp(self.get_argv()[0], self.get_argv().as_ptr()); - io::Error::last_os_error() + // If the program isn't an absolute path, and our environment contains a PATH var, then we + // implement the PATH traversal ourselves so that it honors the child's PATH instead of the + // parent's. This mirrors the logic that exists in glibc's execvpe, except using the + // child's env to fetch PATH. + match maybe_possible_paths { + Some(possible_paths) => { + let mut pending_error = None; + for path in possible_paths { + libc::execve( + path.as_ptr(), + self.get_argv().as_ptr(), + maybe_envp.map(|envp| envp.as_ptr()).unwrap_or_else(|| *sys::os::environ()) + ); + let err = io::Error::last_os_error(); + match err.kind() { + io::ErrorKind::PermissionDenied => { + // If we saw a PermissionDenied, and none of the other entries in + // $PATH are successful, then we'll return the first EACCESS we see. + if pending_error.is_none() { + pending_error = Some(err); + } + }, + // Errors which indicate we failed to find a file are ignored and we try + // the next entry in the path. + io::ErrorKind::NotFound | io::ErrorKind::TimedOut => { + continue + }, + // Any other error means we found a file and couldn't execute it. + _ => { + return err; + } + } + } + if let Some(err) = pending_error { + return err; + } + return io::Error::from_raw_os_error(libc::ENOENT); + }, + _ => { + libc::execve( + self.get_argv()[0], + self.get_argv().as_ptr(), + maybe_envp.map(|envp| envp.as_ptr()).unwrap_or_else(|| *sys::os::environ()) + ); + return io::Error::last_os_error() + } + } } #[cfg(not(any(target_os = "macos", target_os = "freebsd", diff --git a/src/test/run-pass/command-exec.rs b/src/test/run-pass/command-exec.rs index 46b409fb13a84..96f9da67790fc 100644 --- a/src/test/run-pass/command-exec.rs +++ b/src/test/run-pass/command-exec.rs @@ -48,6 +48,13 @@ fn main() { println!("passed"); } + "exec-test5" => { + env::set_var("VARIABLE", "ABC"); + Command::new("definitely-not-a-real-binary").env("VARIABLE", "XYZ").exec(); + assert_eq!(env::var("VARIABLE").unwrap(), "ABC"); + println!("passed"); + } + _ => panic!("unknown argument: {}", arg), } return @@ -72,4 +79,9 @@ fn main() { assert!(output.status.success()); assert!(output.stderr.is_empty()); assert_eq!(output.stdout, b"passed\n"); + + let output = Command::new(&me).arg("exec-test5").output().unwrap(); + assert!(output.status.success()); + assert!(output.stderr.is_empty()); + assert_eq!(output.stdout, b"passed\n"); } From e076a2e829012315dbd4876a47ccf84f6c0d7e31 Mon Sep 17 00:00:00 2001 From: Alex Burka Date: Thu, 1 Nov 2018 03:32:45 +0000 Subject: [PATCH 3/5] enforce unused-must-use lint in macros --- src/librustc_lint/unused.rs | 3 ++- src/test/ui/macros/must-use-in-macro-55516.rs | 21 +++++++++++++++++++ .../ui/macros/must-use-in-macro-55516.stderr | 10 +++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 src/test/ui/macros/must-use-in-macro-55516.rs create mode 100644 src/test/ui/macros/must-use-in-macro-55516.stderr diff --git a/src/librustc_lint/unused.rs b/src/librustc_lint/unused.rs index 96d04253cc485..9ccfecd46fdb2 100644 --- a/src/librustc_lint/unused.rs +++ b/src/librustc_lint/unused.rs @@ -29,7 +29,8 @@ use rustc::hir; declare_lint! { pub UNUSED_MUST_USE, Warn, - "unused result of a type flagged as #[must_use]" + "unused result of a type flagged as #[must_use]", + report_in_external_macro: true } declare_lint! { diff --git a/src/test/ui/macros/must-use-in-macro-55516.rs b/src/test/ui/macros/must-use-in-macro-55516.rs new file mode 100644 index 0000000000000..ad7cc37da52ca --- /dev/null +++ b/src/test/ui/macros/must-use-in-macro-55516.rs @@ -0,0 +1,21 @@ +// Copyright 2018 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// compile-pass +// compile-flags: -Wunused + +// make sure write!() can't hide its unused Result + +fn main() { + use std::fmt::Write; + let mut example = String::new(); + write!(&mut example, "{}", 42); //~WARN must be used +} + diff --git a/src/test/ui/macros/must-use-in-macro-55516.stderr b/src/test/ui/macros/must-use-in-macro-55516.stderr new file mode 100644 index 0000000000000..b03a5806da5c3 --- /dev/null +++ b/src/test/ui/macros/must-use-in-macro-55516.stderr @@ -0,0 +1,10 @@ +warning: unused `std::result::Result` that must be used + --> $DIR/must-use-in-macro-55516.rs:19:5 + | +LL | write!(&mut example, "{}", 42); //~WARN must be used + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-W unused-must-use` implied by `-W unused` + = note: this `Result` may be an `Err` variant, which should be handled + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + From 69caf25c486e002cae7cf68fd78f3d08f36c90d7 Mon Sep 17 00:00:00 2001 From: Alex Burka Date: Fri, 2 Nov 2018 03:26:31 +0000 Subject: [PATCH 4/5] remove unused result in resolve --- src/librustc_resolve/resolve_imports.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/librustc_resolve/resolve_imports.rs b/src/librustc_resolve/resolve_imports.rs index a8907674a0890..285025bed4af4 100644 --- a/src/librustc_resolve/resolve_imports.rs +++ b/src/librustc_resolve/resolve_imports.rs @@ -36,7 +36,6 @@ use syntax_pos::{MultiSpan, Span}; use std::cell::{Cell, RefCell}; use std::collections::BTreeMap; -use std::fmt::Write; use std::{mem, ptr}; /// Contains data for specific types of import directives. @@ -778,17 +777,14 @@ impl<'a, 'b:'a, 'c: 'b> ImportResolver<'a, 'b, 'c> { let msg = format!("`{}` import is ambiguous", name); let mut err = self.session.struct_span_err(span, &msg); - let mut suggestion_choices = String::new(); + let mut suggestion_choices = vec![]; if external_crate.is_some() { - write!(suggestion_choices, "`::{}`", name); + suggestion_choices.push(format!("`::{}`", name)); err.span_label(span, format!("can refer to external crate `::{}`", name)); } if let Some(result) = results.module_scope { - if !suggestion_choices.is_empty() { - suggestion_choices.push_str(" or "); - } - write!(suggestion_choices, "`self::{}`", name); + suggestion_choices.push(format!("`self::{}`", name)); if uniform_paths_feature { err.span_label(result.span, format!("can refer to `self::{}`", name)); @@ -801,7 +797,7 @@ impl<'a, 'b:'a, 'c: 'b> ImportResolver<'a, 'b, 'c> { err.span_label(result.span, format!("shadowed by block-scoped `{}`", name)); } - err.help(&format!("write {} explicitly instead", suggestion_choices)); + err.help(&format!("write {} explicitly instead", suggestion_choices.join(" or "))); if uniform_paths_feature { err.note("relative `use` paths enabled by `#![feature(uniform_paths)]`"); } else { From 39fa89bb32ed00875f93862314735befed3e0138 Mon Sep 17 00:00:00 2001 From: Alex Burka Date: Sat, 3 Nov 2018 05:03:30 +0000 Subject: [PATCH 5/5] fix test fallout --- src/test/run-pass/impl-trait/example-calendar.rs | 6 +++--- src/test/run-pass/macros/colorful-write-macros.rs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/test/run-pass/impl-trait/example-calendar.rs b/src/test/run-pass/impl-trait/example-calendar.rs index 6cf06d1562104..e6dd421f48f51 100644 --- a/src/test/run-pass/impl-trait/example-calendar.rs +++ b/src/test/run-pass/impl-trait/example-calendar.rs @@ -310,10 +310,10 @@ trait IteratorExt: Iterator + Sized { where Self::Item: std::fmt::Display { let mut s = String::new(); if let Some(e) = self.next() { - write!(s, "{}", e); + write!(s, "{}", e).unwrap(); for e in self { s.push_str(sep); - write!(s, "{}", e); + write!(s, "{}", e).unwrap(); } } s @@ -537,7 +537,7 @@ fn format_weeks(it: impl Iterator) -> impl Iterator2}", d.day()); + write!(buf, " {:>2}", d.day()).unwrap(); } // Insert more filler at the end to fill up the remainder of the week, diff --git a/src/test/run-pass/macros/colorful-write-macros.rs b/src/test/run-pass/macros/colorful-write-macros.rs index 7c557eb2bd079..ee597f11c6a9d 100644 --- a/src/test/run-pass/macros/colorful-write-macros.rs +++ b/src/test/run-pass/macros/colorful-write-macros.rs @@ -27,18 +27,18 @@ impl fmt::Write for Bar { } fn borrowing_writer_from_struct_and_formatting_struct_field(foo: Foo) { - write!(foo.writer, "{}", foo.other); + write!(foo.writer, "{}", foo.other).unwrap(); } fn main() { let mut w = Vec::new(); - write!(&mut w as &mut Write, ""); - write!(&mut w, ""); // should coerce + write!(&mut w as &mut Write, "").unwrap(); + write!(&mut w, "").unwrap(); // should coerce println!("ok"); let mut s = Bar; { use std::fmt::Write; - write!(&mut s, "test"); + write!(&mut s, "test").unwrap(); } }