Description
Hi,
there seems to be a problem with the borrow checker that allows references to exist after the borrowed data has been dropped.
I wrote a wrapper struct for a HashMap that would allow me to add frequencies in a manner similar to the Counter known from Python.
When using the +=
operator to add two of these structs the borrow checker seems to be unable to detect when borrowed values are dropped. The code compiles allowing invalid pointers.
The compiler rejects the code when .add_assign()
is called explicitly.
I tried this code (Playground):
use std::collections::HashMap;
use std::hash::Hash;
use std::ops::AddAssign;
pub fn main() {
works();
panics();
}
pub struct Counter<K> {
map: HashMap<K, usize>,
}
impl<K> AddAssign for Counter<K>
where
K: Hash + Eq,
{
fn add_assign(&mut self, rhs: Counter<K>) {
rhs.map.into_iter().for_each(|(key, val)| {
let count = self.map.entry(key).or_insert(0);
*count += val;
});
}
}
/// reliably prints correct
pub fn works() {
let mut acc = Counter{map: HashMap::new()};
for line in vec!["12345678".to_string(), "12345678".to_string()] {
let v: Vec<&str> = line.split_whitespace().collect();
println!("accumulator before add_assign {:?}", acc.map);
let mut map = HashMap::new();
for str_ref in v {
{
let count = map.entry(str_ref).or_insert(0);
*count += 1;
}
}
let cnt2 = Counter{map};
acc += cnt2;
println!("accumulator after add_assign {:?}", acc.map);
}
}
/// often times crashes, if not prints invalid strings
pub fn panics() {
let mut acc = Counter{map: HashMap::new()};
for line in vec!["123456789".to_string(), "12345678".to_string()] {
let v: Vec<&str> = line.split_whitespace().collect();
println!("accumulator before add_assign {:?}", acc.map);
let mut map = HashMap::new();
for str_ref in v {
{
let count = map.entry(str_ref).or_insert(0);
*count += 1;
}
}
let cnt2 = Counter{map};
acc += cnt2;
println!("accumulator after add_assign {:?}", acc.map);
// line gets dropped here but references are kept in acc.map
}
}
I expected to see this happen:
The compiler should reject this code
Instead, this happened:
The program compiles and results in unpredictable behaviour. Sometimes the program panics at runtime, sometimes invalid strings are printed.
Meta
rustc --version --verbose
:
rustc 1.26.0 (a775680 2018-05-07)
binary: rustc
commit-hash: a775680
commit-date: 2018-05-07
host: x86_64-unknown-linux-gnu
release: 1.26.0
LLVM version: 6.0
Backtrace:
thread 'main' panicked at 'byte index 3 is not a char boundary; it is inside 'B' (bytes 2..3) of `� B��� `', libcore/str/mod.rs:2252:5
stack backtrace:
0: std::sys::unix::backtrace::tracing::imp::unwind_backtrace
at libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
1: std::sys_common::backtrace::print
at libstd/sys_common/backtrace.rs:71
at libstd/sys_common/backtrace.rs:59
2: std::panicking::default_hook::{{closure}}
at libstd/panicking.rs:207
3: std::panicking::default_hook
at libstd/panicking.rs:223
4: std::panicking::rust_panic_with_hook
at libstd/panicking.rs:402
5: std::panicking::begin_panic_fmt
at libstd/panicking.rs:349
6: rust_begin_unwind
at libstd/panicking.rs:325
7: core::panicking::panic_fmt
at libcore/panicking.rs:72
8: core::str::slice_error_fail
at libcore/str/mod.rs:0
9: core::str::traits::<impl core::slice::SliceIndex<str> for core::ops::range::Range<usize>>::index::{{closure}}
at libcore/str/mod.rs:1915
10: <str as core::fmt::Debug>::fmt
at libcore/option.rs:376
at libcore/str/mod.rs:1915
at libcore/str/mod.rs:1697
at libcore/fmt/mod.rs:1818
11: <&'a T as core::fmt::Debug>::fmt
at /checkout/src/libcore/fmt/mod.rs:1768
12: <&'a T as core::fmt::Debug>::fmt
at /checkout/src/libcore/fmt/mod.rs:1768
13: core::fmt::builders::DebugMap::entry
at libcore/fmt/builders.rs:502
at libcore/result.rs:621
at libcore/fmt/builders.rs:486
14: core::fmt::builders::DebugMap::entries
at /checkout/src/libcore/fmt/builders.rs:520
15: <std::collections::hash::map::HashMap<K, V, S> as core::fmt::Debug>::fmt
at /checkout/src/libstd/collections/hash/map.rs:1487
16: core::fmt::write
at libcore/fmt/mod.rs:1100
at libcore/fmt/mod.rs:1047
17: <std::io::stdio::Stdout as std::io::Write>::write_fmt
at libstd/io/mod.rs:1169
at libstd/io/stdio.rs:459
18: <std::thread::local::LocalKey<T>>::try_with
at libstd/io/stdio.rs:686
at libstd/thread/local.rs:290
19: std::io::stdio::_print
at libstd/io/stdio.rs:680
at libstd/io/stdio.rs:701
20: counter::panics
at src/main.rs:51
21: counter::main
at src/main.rs:7
22: std::rt::lang_start::{{closure}}
at /checkout/src/libstd/rt.rs:74
23: std::panicking::try::do_call
at libstd/rt.rs:59
at libstd/panicking.rs:306
24: __rust_maybe_catch_panic
at libpanic_unwind/lib.rs:102
25: std::rt::lang_start_internal
at libstd/panicking.rs:285
at libstd/panic.rs:361
at libstd/rt.rs:58
26: std::rt::lang_start
at /checkout/src/libstd/rt.rs:74
27: main
28: __libc_start_main
29: _start