FlatMap, a vector map for extra - #9653

Closed
toffaletti wants to merge 1 commit into
rust-lang:masterfrom
toffaletti:flatmap
Closed

FlatMap, a vector map for extra#9653
toffaletti wants to merge 1 commit into
rust-lang:masterfrom
toffaletti:flatmap

Conversation

@toffaletti

Copy link
Copy Markdown
Contributor

I don't expect this to be merged as-is, but I'd like some eyes on what I've done so far. I'd like to implement a FlatSet in this file also to have as much feature parity with hashmap.rs as possible.

Comment threadsrc/libextra/flatmap.rs Outdated

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must be a better way to do this, but I struggled to find one.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this compile?

 match self.find_mut(&k) {
Some(val) => {
found(&k, val, a);
return val
}
None => ()
}
let v = not_found(&k, a);
self.data.push((k,v));
match self.data.mut_rev_iter().next() {
Some(&(_, ref mut val)) => val,
None => unreachable!(),
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, but I was referring to the match self.data.mut_rev_iter().next() line, wanting to find a better way to do that.

src/libextra/flatmap.rs:71:8: 71:17 error: cannot borrow `(*self).data` as mutable more than once at a time
src/libextra/flatmap.rs:71 self.data.push((k,v));
^~~~~~~~~
src/libextra/flatmap.rs:63:14: 63:19 note: second borrow of `(*self).data` as mutable occurs here
src/libextra/flatmap.rs:63 match self.find_mut(&k) {
^~~~~
src/libextra/flatmap.rs:72:14: 72:23 error: cannot borrow `(*(*self).data)[]` as mutable more than once at a time
src/libextra/flatmap.rs:72 match self.data.mut_rev_iter().next() {
^~~~~~~~~
src/libextra/flatmap.rs:63:14: 63:19 note: second borrow of `(*(*self).data)[]` as mutable occurs here
src/libextra/flatmap.rs:63 match self.find_mut(&k) {

@sfackler

Copy link
Copy Markdown
Member

extra::smallintmap should probably be deleted if this goes through.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

Maybe. It looks like smallintmap's keys are the indexes into the vector, so it can be even smaller than FlatMap under the right conditions.

@alexcrichton

Copy link
Copy Markdown
Member

This is some nice code, thanks! That being said, I'm not sure how many more containers libextra needs at the moment. This would be an excellent candidate for the "package incubator" type situation that we envision for libextra, but I'm not certain that this needs to be merged at this time.

I could be wrong though, does this have a good use case that I'm not seeing? It seems that this has O(n) on almost all operations which seems undesirable? I could be overlooking something though.

@thestinger

Copy link
Copy Markdown
Contributor

@sfackler: smallintmap has O(1) insertion, deletion and "search" - isn't this O(n)?

@sfackler

Copy link
Copy Markdown
Member

Oh yeah, nevermind.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

@alexcrichton I haven't benchmarked this implementation, but this type of container is generally faster than a hash map for small data sets like say http headers.

I'm not taking advantage of sorting yet, but if you have sorted keys this type of container can be great for large immutable data sets too where the overhead of holes in a hash map becomes costly.

Prior art:
http://www.boost.org/doc/libs/1_54_0/doc/html/boost/container/flat_map.html

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

smallintmap is great for things like file descriptor to waiter mappings where you're fine possibly trading a little memory or insertion speed for a lot of speed on lookup.

@alexcrichton

Copy link
Copy Markdown
Member

Oh interesting! This would actually be an excellent type to use in TLS currently as well...

I'm always a fan of more cool stuff in rust, and others may want to weigh in on this as well, but this feels like it should wait for our "incubator" to manifest itself.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

I added some benchmarks and modified the code to keep the vector sorted.

Before sorting:

test bench::flatmap_find_10 ... bench: 395 ns/iter (+/- 8)
test bench::flatmap_find_50 ... bench: 1065 ns/iter (+/- 30)
test bench::flatmap_insert_10 ... bench: 2865 ns/iter (+/- 224)
test bench::flatmap_insert_50 ... bench: 37811 ns/iter (+/- 889)
test bench::hashmap_find_10 ... bench: 446 ns/iter (+/- 21)
test bench::hashmap_find_50 ... bench: 482 ns/iter (+/- 56)
test bench::hashmap_insert_10 ... bench: 4943 ns/iter (+/- 148)
test bench::hashmap_insert_50 ... bench: 34150 ns/iter (+/- 1005)

After sorting:

test bench::flatmap_find_10 ... bench: 166 ns/iter (+/- 7)
test bench::flatmap_find_50 ... bench: 218 ns/iter (+/- 20)
test bench::flatmap_insert_10 ... bench: 3161 ns/iter (+/- 251)
test bench::flatmap_insert_50 ... bench: 17743 ns/iter (+/- 696)
test bench::hashmap_find_10 ... bench: 447 ns/iter (+/- 24)
test bench::hashmap_find_50 ... bench: 480 ns/iter (+/- 23)
test bench::hashmap_insert_10 ... bench: 4956 ns/iter (+/- 277)
test bench::hashmap_insert_50 ... bench: 34484 ns/iter (+/- 5944)

@huonw

huonw commented Oct 1, 2013

Copy link
Copy Markdown
Contributor

Is it worth having the extra restriction of Ord? Someone can use treemap if they have that extra structure. (It's a trade-off between speed and generality.)

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

Good point, TotalOrd is the only trait needed. Fixed.

@huonw

huonw commented Oct 1, 2013

Copy link
Copy Markdown
Contributor

Erm, that's not a relaxation: fewer types are TotalOrd than Ord!

I actually meant this should be totally unordered, since we have other maps that cover that case. I.e. this should be a "pure" association list, which is the simplest map, and the only one that works for types that only implement Eq.

@erickt

Copy link
Copy Markdown
Contributor

@toffaletti: awesome! I kept meaning to add something like this, but never got around to it. Two comments:

  • Is FlatMap a common name for this? I've always heard of this pattern being called an association list. For example, here's OCaml implementation. Perhaps this should be named AssocMap?
  • Should we have a copyless from_owned_vec(v: ~[(K,V)]) -> FlatMap<K,V> and .into_owned_vec(self) -> ~[(K,V)]?

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

@huonw I see, I thought you just meant it was silly I had Eq+Ord+TotalOrd. I did originally implement it without ordering because I hadn't gotten around to writing the sorting code yet. Perhaps it is my lack of imagination, but I have a hard time coming up with a use case for FlatMap where the keys couldn't be ordered. An important property of FlatMap is that it is fast and the ordering is a part of that. It has a few advantages over TreeMap like less memory usage, fewer allocations, and faster lookups.

@erickt boost calls it flat_map. The wikipedia link you posted says an association list is a linked list. flat map is a sorted vector, more like an array backed binary heap.

FlatMap exploits how amazingly fast modern hardware is at accessing contiguous memory. It is memory efficient and faster than you might expect.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

I turned the benchmark code into a macro and added TreeMap, but it seems to be misbehaving. I randomly see benchmarks take 0 ns/iter. I couldn't find a bug reporting that problem, can anyone verify? I've made the imports in flatmap.rs so you can just rust test flatmap.rs --bench. Thanks for all the help and feedback everyone.

@alexcrichton

Copy link
Copy Markdown
Member

If you see 0 ns/iter it often means that your iterations are taking too long, I believe the current threshold is 1ms for the maximum amount of time your iterations should take.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

I made https://github.com/toffaletti/rust/tree/flatmap-used to test some real-world usage of FlatMap by replacing libextra usages of TreeMap with FlatMap. It exposed some issues with FlatMap that I've fixed to make stage2-check pass.

One thing worth mentioning is the changes ended up being much farther reaching than I'd hoped or expected because many things using the json module were depending on it using TreeMap to represent json::Object which seems like a code smell.

I'm not sure how to quantify this for merging, mostly it was just a fun and educational experiment for me. I think @alexcrichton is probably right saying it can wait.

@alexcrichton

Copy link
Copy Markdown
Member

As I mentioned on #9816, if we don't end up merging this, I would highly recommend putting this in a rustpkg repo on http://hiho.io/rust-ci/, because I'm sure others will find this useful!

@pnkfelixpnkfelix mentioned this pull request Oct 15, 2013
@brson

Copy link
Copy Markdown
Contributor

I too think this is a good data structure, but that we should not put it in libextra since it is soon to be dissolved and moved to various other places, and this probably doesn't belong in std. It would be nice to have a discussion of what data structures we need in std, and whether we should have a single, well-supported external package for more advanced or specialized data structures like this one.

@brson

Copy link
Copy Markdown
Contributor

Closing for the above reasons.

@brsonbrson closed this Oct 16, 2013
flip1995 pushed a commit to flip1995/rust that referenced this pull request Oct 20, 2022
fix `box-default` linting `no_std` non-boxes
This fixesrust-lang#9653 by doing the check against the `Box` type correctly even if `Box` isn't there, as in `no_std` code. Thanks to `@lukas-code` for opening the issue and supplying a reproducer!
---
changelog: none
U007D pushed a commit to U007D/rust-mos that referenced this pull request Aug 21, 2026
9653: minor: cov-mark r=Veykril a=Veykril
bors r+
Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@toffaletti@sfackler@alexcrichton@thestinger@huonw@erickt@brson@bluss
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

FlatMap, a vector map for extra - #9653

Closed
toffaletti wants to merge 1 commit into
rust-lang:masterfrom
toffaletti:flatmap
Closed

FlatMap, a vector map for extra#9653
toffaletti wants to merge 1 commit into
rust-lang:masterfrom
toffaletti:flatmap

Conversation

@toffaletti

Copy link
Copy Markdown
Contributor

I don't expect this to be merged as-is, but I'd like some eyes on what I've done so far. I'd like to implement a FlatSet in this file also to have as much feature parity with hashmap.rs as possible.

Comment threadsrc/libextra/flatmap.rs Outdated

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must be a better way to do this, but I struggled to find one.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this compile?

 match self.find_mut(&k) {
Some(val) => {
found(&k, val, a);
return val
}
None => ()
}
let v = not_found(&k, a);
self.data.push((k,v));
match self.data.mut_rev_iter().next() {
Some(&(_, ref mut val)) => val,
None => unreachable!(),
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, but I was referring to the match self.data.mut_rev_iter().next() line, wanting to find a better way to do that.

src/libextra/flatmap.rs:71:8: 71:17 error: cannot borrow `(*self).data` as mutable more than once at a time
src/libextra/flatmap.rs:71 self.data.push((k,v));
^~~~~~~~~
src/libextra/flatmap.rs:63:14: 63:19 note: second borrow of `(*self).data` as mutable occurs here
src/libextra/flatmap.rs:63 match self.find_mut(&k) {
^~~~~
src/libextra/flatmap.rs:72:14: 72:23 error: cannot borrow `(*(*self).data)[]` as mutable more than once at a time
src/libextra/flatmap.rs:72 match self.data.mut_rev_iter().next() {
^~~~~~~~~
src/libextra/flatmap.rs:63:14: 63:19 note: second borrow of `(*(*self).data)[]` as mutable occurs here
src/libextra/flatmap.rs:63 match self.find_mut(&k) {

@sfackler

Copy link
Copy Markdown
Member

extra::smallintmap should probably be deleted if this goes through.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

Maybe. It looks like smallintmap's keys are the indexes into the vector, so it can be even smaller than FlatMap under the right conditions.

@alexcrichton

Copy link
Copy Markdown
Member

This is some nice code, thanks! That being said, I'm not sure how many more containers libextra needs at the moment. This would be an excellent candidate for the "package incubator" type situation that we envision for libextra, but I'm not certain that this needs to be merged at this time.

I could be wrong though, does this have a good use case that I'm not seeing? It seems that this has O(n) on almost all operations which seems undesirable? I could be overlooking something though.

@thestinger

Copy link
Copy Markdown
Contributor

@sfackler: smallintmap has O(1) insertion, deletion and "search" - isn't this O(n)?

@sfackler

Copy link
Copy Markdown
Member

Oh yeah, nevermind.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

@alexcrichton I haven't benchmarked this implementation, but this type of container is generally faster than a hash map for small data sets like say http headers.

I'm not taking advantage of sorting yet, but if you have sorted keys this type of container can be great for large immutable data sets too where the overhead of holes in a hash map becomes costly.

Prior art:
http://www.boost.org/doc/libs/1_54_0/doc/html/boost/container/flat_map.html

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

smallintmap is great for things like file descriptor to waiter mappings where you're fine possibly trading a little memory or insertion speed for a lot of speed on lookup.

@alexcrichton

Copy link
Copy Markdown
Member

Oh interesting! This would actually be an excellent type to use in TLS currently as well...

I'm always a fan of more cool stuff in rust, and others may want to weigh in on this as well, but this feels like it should wait for our "incubator" to manifest itself.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

I added some benchmarks and modified the code to keep the vector sorted.

Before sorting:

test bench::flatmap_find_10 ... bench: 395 ns/iter (+/- 8)
test bench::flatmap_find_50 ... bench: 1065 ns/iter (+/- 30)
test bench::flatmap_insert_10 ... bench: 2865 ns/iter (+/- 224)
test bench::flatmap_insert_50 ... bench: 37811 ns/iter (+/- 889)
test bench::hashmap_find_10 ... bench: 446 ns/iter (+/- 21)
test bench::hashmap_find_50 ... bench: 482 ns/iter (+/- 56)
test bench::hashmap_insert_10 ... bench: 4943 ns/iter (+/- 148)
test bench::hashmap_insert_50 ... bench: 34150 ns/iter (+/- 1005)

After sorting:

test bench::flatmap_find_10 ... bench: 166 ns/iter (+/- 7)
test bench::flatmap_find_50 ... bench: 218 ns/iter (+/- 20)
test bench::flatmap_insert_10 ... bench: 3161 ns/iter (+/- 251)
test bench::flatmap_insert_50 ... bench: 17743 ns/iter (+/- 696)
test bench::hashmap_find_10 ... bench: 447 ns/iter (+/- 24)
test bench::hashmap_find_50 ... bench: 480 ns/iter (+/- 23)
test bench::hashmap_insert_10 ... bench: 4956 ns/iter (+/- 277)
test bench::hashmap_insert_50 ... bench: 34484 ns/iter (+/- 5944)

@huonw

huonw commented Oct 1, 2013

Copy link
Copy Markdown
Contributor

Is it worth having the extra restriction of Ord? Someone can use treemap if they have that extra structure. (It's a trade-off between speed and generality.)

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

Good point, TotalOrd is the only trait needed. Fixed.

@huonw

huonw commented Oct 1, 2013

Copy link
Copy Markdown
Contributor

Erm, that's not a relaxation: fewer types are TotalOrd than Ord!

I actually meant this should be totally unordered, since we have other maps that cover that case. I.e. this should be a "pure" association list, which is the simplest map, and the only one that works for types that only implement Eq.

@erickt

Copy link
Copy Markdown
Contributor

@toffaletti: awesome! I kept meaning to add something like this, but never got around to it. Two comments:

  • Is FlatMap a common name for this? I've always heard of this pattern being called an association list. For example, here's OCaml implementation. Perhaps this should be named AssocMap?
  • Should we have a copyless from_owned_vec(v: ~[(K,V)]) -> FlatMap<K,V> and .into_owned_vec(self) -> ~[(K,V)]?

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

@huonw I see, I thought you just meant it was silly I had Eq+Ord+TotalOrd. I did originally implement it without ordering because I hadn't gotten around to writing the sorting code yet. Perhaps it is my lack of imagination, but I have a hard time coming up with a use case for FlatMap where the keys couldn't be ordered. An important property of FlatMap is that it is fast and the ordering is a part of that. It has a few advantages over TreeMap like less memory usage, fewer allocations, and faster lookups.

@erickt boost calls it flat_map. The wikipedia link you posted says an association list is a linked list. flat map is a sorted vector, more like an array backed binary heap.

FlatMap exploits how amazingly fast modern hardware is at accessing contiguous memory. It is memory efficient and faster than you might expect.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

I turned the benchmark code into a macro and added TreeMap, but it seems to be misbehaving. I randomly see benchmarks take 0 ns/iter. I couldn't find a bug reporting that problem, can anyone verify? I've made the imports in flatmap.rs so you can just rust test flatmap.rs --bench. Thanks for all the help and feedback everyone.

@alexcrichton

Copy link
Copy Markdown
Member

If you see 0 ns/iter it often means that your iterations are taking too long, I believe the current threshold is 1ms for the maximum amount of time your iterations should take.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

I made https://github.com/toffaletti/rust/tree/flatmap-used to test some real-world usage of FlatMap by replacing libextra usages of TreeMap with FlatMap. It exposed some issues with FlatMap that I've fixed to make stage2-check pass.

One thing worth mentioning is the changes ended up being much farther reaching than I'd hoped or expected because many things using the json module were depending on it using TreeMap to represent json::Object which seems like a code smell.

I'm not sure how to quantify this for merging, mostly it was just a fun and educational experiment for me. I think @alexcrichton is probably right saying it can wait.

@alexcrichton

Copy link
Copy Markdown
Member

As I mentioned on #9816, if we don't end up merging this, I would highly recommend putting this in a rustpkg repo on http://hiho.io/rust-ci/, because I'm sure others will find this useful!

@pnkfelixpnkfelix mentioned this pull request Oct 15, 2013
@brson

Copy link
Copy Markdown
Contributor

I too think this is a good data structure, but that we should not put it in libextra since it is soon to be dissolved and moved to various other places, and this probably doesn't belong in std. It would be nice to have a discussion of what data structures we need in std, and whether we should have a single, well-supported external package for more advanced or specialized data structures like this one.

@brson

Copy link
Copy Markdown
Contributor

Closing for the above reasons.

@brsonbrson closed this Oct 16, 2013
flip1995 pushed a commit to flip1995/rust that referenced this pull request Oct 20, 2022
fix `box-default` linting `no_std` non-boxes
This fixesrust-lang#9653 by doing the check against the `Box` type correctly even if `Box` isn't there, as in `no_std` code. Thanks to `@lukas-code` for opening the issue and supplying a reproducer!
---
changelog: none
U007D pushed a commit to U007D/rust-mos that referenced this pull request Aug 21, 2026
9653: minor: cov-mark r=Veykril a=Veykril
bors r+
Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@toffaletti@sfackler@alexcrichton@thestinger@huonw@erickt@brson@bluss
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

FlatMap, a vector map for extra - #9653

Closed
toffaletti wants to merge 1 commit into
rust-lang:masterfrom
toffaletti:flatmap
Closed

FlatMap, a vector map for extra#9653
toffaletti wants to merge 1 commit into
rust-lang:masterfrom
toffaletti:flatmap

Conversation

@toffaletti

Copy link
Copy Markdown
Contributor

I don't expect this to be merged as-is, but I'd like some eyes on what I've done so far. I'd like to implement a FlatSet in this file also to have as much feature parity with hashmap.rs as possible.

Comment threadsrc/libextra/flatmap.rs Outdated

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must be a better way to do this, but I struggled to find one.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this compile?

 match self.find_mut(&k) {
Some(val) => {
found(&k, val, a);
return val
}
None => ()
}
let v = not_found(&k, a);
self.data.push((k,v));
match self.data.mut_rev_iter().next() {
Some(&(_, ref mut val)) => val,
None => unreachable!(),
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, but I was referring to the match self.data.mut_rev_iter().next() line, wanting to find a better way to do that.

src/libextra/flatmap.rs:71:8: 71:17 error: cannot borrow `(*self).data` as mutable more than once at a time
src/libextra/flatmap.rs:71 self.data.push((k,v));
^~~~~~~~~
src/libextra/flatmap.rs:63:14: 63:19 note: second borrow of `(*self).data` as mutable occurs here
src/libextra/flatmap.rs:63 match self.find_mut(&k) {
^~~~~
src/libextra/flatmap.rs:72:14: 72:23 error: cannot borrow `(*(*self).data)[]` as mutable more than once at a time
src/libextra/flatmap.rs:72 match self.data.mut_rev_iter().next() {
^~~~~~~~~
src/libextra/flatmap.rs:63:14: 63:19 note: second borrow of `(*(*self).data)[]` as mutable occurs here
src/libextra/flatmap.rs:63 match self.find_mut(&k) {

@sfackler

Copy link
Copy Markdown
Member

extra::smallintmap should probably be deleted if this goes through.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

Maybe. It looks like smallintmap's keys are the indexes into the vector, so it can be even smaller than FlatMap under the right conditions.

@alexcrichton

Copy link
Copy Markdown
Member

This is some nice code, thanks! That being said, I'm not sure how many more containers libextra needs at the moment. This would be an excellent candidate for the "package incubator" type situation that we envision for libextra, but I'm not certain that this needs to be merged at this time.

I could be wrong though, does this have a good use case that I'm not seeing? It seems that this has O(n) on almost all operations which seems undesirable? I could be overlooking something though.

@thestinger

Copy link
Copy Markdown
Contributor

@sfackler: smallintmap has O(1) insertion, deletion and "search" - isn't this O(n)?

@sfackler

Copy link
Copy Markdown
Member

Oh yeah, nevermind.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

@alexcrichton I haven't benchmarked this implementation, but this type of container is generally faster than a hash map for small data sets like say http headers.

I'm not taking advantage of sorting yet, but if you have sorted keys this type of container can be great for large immutable data sets too where the overhead of holes in a hash map becomes costly.

Prior art:
http://www.boost.org/doc/libs/1_54_0/doc/html/boost/container/flat_map.html

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

smallintmap is great for things like file descriptor to waiter mappings where you're fine possibly trading a little memory or insertion speed for a lot of speed on lookup.

@alexcrichton

Copy link
Copy Markdown
Member

Oh interesting! This would actually be an excellent type to use in TLS currently as well...

I'm always a fan of more cool stuff in rust, and others may want to weigh in on this as well, but this feels like it should wait for our "incubator" to manifest itself.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

I added some benchmarks and modified the code to keep the vector sorted.

Before sorting:

test bench::flatmap_find_10 ... bench: 395 ns/iter (+/- 8)
test bench::flatmap_find_50 ... bench: 1065 ns/iter (+/- 30)
test bench::flatmap_insert_10 ... bench: 2865 ns/iter (+/- 224)
test bench::flatmap_insert_50 ... bench: 37811 ns/iter (+/- 889)
test bench::hashmap_find_10 ... bench: 446 ns/iter (+/- 21)
test bench::hashmap_find_50 ... bench: 482 ns/iter (+/- 56)
test bench::hashmap_insert_10 ... bench: 4943 ns/iter (+/- 148)
test bench::hashmap_insert_50 ... bench: 34150 ns/iter (+/- 1005)

After sorting:

test bench::flatmap_find_10 ... bench: 166 ns/iter (+/- 7)
test bench::flatmap_find_50 ... bench: 218 ns/iter (+/- 20)
test bench::flatmap_insert_10 ... bench: 3161 ns/iter (+/- 251)
test bench::flatmap_insert_50 ... bench: 17743 ns/iter (+/- 696)
test bench::hashmap_find_10 ... bench: 447 ns/iter (+/- 24)
test bench::hashmap_find_50 ... bench: 480 ns/iter (+/- 23)
test bench::hashmap_insert_10 ... bench: 4956 ns/iter (+/- 277)
test bench::hashmap_insert_50 ... bench: 34484 ns/iter (+/- 5944)

@huonw

huonw commented Oct 1, 2013

Copy link
Copy Markdown
Contributor

Is it worth having the extra restriction of Ord? Someone can use treemap if they have that extra structure. (It's a trade-off between speed and generality.)

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

Good point, TotalOrd is the only trait needed. Fixed.

@huonw

huonw commented Oct 1, 2013

Copy link
Copy Markdown
Contributor

Erm, that's not a relaxation: fewer types are TotalOrd than Ord!

I actually meant this should be totally unordered, since we have other maps that cover that case. I.e. this should be a "pure" association list, which is the simplest map, and the only one that works for types that only implement Eq.

@erickt

Copy link
Copy Markdown
Contributor

@toffaletti: awesome! I kept meaning to add something like this, but never got around to it. Two comments:

  • Is FlatMap a common name for this? I've always heard of this pattern being called an association list. For example, here's OCaml implementation. Perhaps this should be named AssocMap?
  • Should we have a copyless from_owned_vec(v: ~[(K,V)]) -> FlatMap<K,V> and .into_owned_vec(self) -> ~[(K,V)]?

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

@huonw I see, I thought you just meant it was silly I had Eq+Ord+TotalOrd. I did originally implement it without ordering because I hadn't gotten around to writing the sorting code yet. Perhaps it is my lack of imagination, but I have a hard time coming up with a use case for FlatMap where the keys couldn't be ordered. An important property of FlatMap is that it is fast and the ordering is a part of that. It has a few advantages over TreeMap like less memory usage, fewer allocations, and faster lookups.

@erickt boost calls it flat_map. The wikipedia link you posted says an association list is a linked list. flat map is a sorted vector, more like an array backed binary heap.

FlatMap exploits how amazingly fast modern hardware is at accessing contiguous memory. It is memory efficient and faster than you might expect.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

I turned the benchmark code into a macro and added TreeMap, but it seems to be misbehaving. I randomly see benchmarks take 0 ns/iter. I couldn't find a bug reporting that problem, can anyone verify? I've made the imports in flatmap.rs so you can just rust test flatmap.rs --bench. Thanks for all the help and feedback everyone.

@alexcrichton

Copy link
Copy Markdown
Member

If you see 0 ns/iter it often means that your iterations are taking too long, I believe the current threshold is 1ms for the maximum amount of time your iterations should take.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

I made https://github.com/toffaletti/rust/tree/flatmap-used to test some real-world usage of FlatMap by replacing libextra usages of TreeMap with FlatMap. It exposed some issues with FlatMap that I've fixed to make stage2-check pass.

One thing worth mentioning is the changes ended up being much farther reaching than I'd hoped or expected because many things using the json module were depending on it using TreeMap to represent json::Object which seems like a code smell.

I'm not sure how to quantify this for merging, mostly it was just a fun and educational experiment for me. I think @alexcrichton is probably right saying it can wait.

@alexcrichton

Copy link
Copy Markdown
Member

As I mentioned on #9816, if we don't end up merging this, I would highly recommend putting this in a rustpkg repo on http://hiho.io/rust-ci/, because I'm sure others will find this useful!

@pnkfelixpnkfelix mentioned this pull request Oct 15, 2013
@brson

Copy link
Copy Markdown
Contributor

I too think this is a good data structure, but that we should not put it in libextra since it is soon to be dissolved and moved to various other places, and this probably doesn't belong in std. It would be nice to have a discussion of what data structures we need in std, and whether we should have a single, well-supported external package for more advanced or specialized data structures like this one.

@brson

Copy link
Copy Markdown
Contributor

Closing for the above reasons.

@brsonbrson closed this Oct 16, 2013
flip1995 pushed a commit to flip1995/rust that referenced this pull request Oct 20, 2022
fix `box-default` linting `no_std` non-boxes
This fixesrust-lang#9653 by doing the check against the `Box` type correctly even if `Box` isn't there, as in `no_std` code. Thanks to `@lukas-code` for opening the issue and supplying a reproducer!
---
changelog: none
U007D pushed a commit to U007D/rust-mos that referenced this pull request Aug 21, 2026
9653: minor: cov-mark r=Veykril a=Veykril
bors r+
Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@toffaletti@sfackler@alexcrichton@thestinger@huonw@erickt@brson@bluss
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

FlatMap, a vector map for extra - #9653

Closed
toffaletti wants to merge 1 commit into
rust-lang:masterfrom
toffaletti:flatmap
Closed

FlatMap, a vector map for extra#9653
toffaletti wants to merge 1 commit into
rust-lang:masterfrom
toffaletti:flatmap

Conversation

@toffaletti

Copy link
Copy Markdown
Contributor

I don't expect this to be merged as-is, but I'd like some eyes on what I've done so far. I'd like to implement a FlatSet in this file also to have as much feature parity with hashmap.rs as possible.

Comment threadsrc/libextra/flatmap.rs Outdated

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must be a better way to do this, but I struggled to find one.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this compile?

 match self.find_mut(&k) {
Some(val) => {
found(&k, val, a);
return val
}
None => ()
}
let v = not_found(&k, a);
self.data.push((k,v));
match self.data.mut_rev_iter().next() {
Some(&(_, ref mut val)) => val,
None => unreachable!(),
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, but I was referring to the match self.data.mut_rev_iter().next() line, wanting to find a better way to do that.

src/libextra/flatmap.rs:71:8: 71:17 error: cannot borrow `(*self).data` as mutable more than once at a time
src/libextra/flatmap.rs:71 self.data.push((k,v));
^~~~~~~~~
src/libextra/flatmap.rs:63:14: 63:19 note: second borrow of `(*self).data` as mutable occurs here
src/libextra/flatmap.rs:63 match self.find_mut(&k) {
^~~~~
src/libextra/flatmap.rs:72:14: 72:23 error: cannot borrow `(*(*self).data)[]` as mutable more than once at a time
src/libextra/flatmap.rs:72 match self.data.mut_rev_iter().next() {
^~~~~~~~~
src/libextra/flatmap.rs:63:14: 63:19 note: second borrow of `(*(*self).data)[]` as mutable occurs here
src/libextra/flatmap.rs:63 match self.find_mut(&k) {

@sfackler

Copy link
Copy Markdown
Member

extra::smallintmap should probably be deleted if this goes through.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

Maybe. It looks like smallintmap's keys are the indexes into the vector, so it can be even smaller than FlatMap under the right conditions.

@alexcrichton

Copy link
Copy Markdown
Member

This is some nice code, thanks! That being said, I'm not sure how many more containers libextra needs at the moment. This would be an excellent candidate for the "package incubator" type situation that we envision for libextra, but I'm not certain that this needs to be merged at this time.

I could be wrong though, does this have a good use case that I'm not seeing? It seems that this has O(n) on almost all operations which seems undesirable? I could be overlooking something though.

@thestinger

Copy link
Copy Markdown
Contributor

@sfackler: smallintmap has O(1) insertion, deletion and "search" - isn't this O(n)?

@sfackler

Copy link
Copy Markdown
Member

Oh yeah, nevermind.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

@alexcrichton I haven't benchmarked this implementation, but this type of container is generally faster than a hash map for small data sets like say http headers.

I'm not taking advantage of sorting yet, but if you have sorted keys this type of container can be great for large immutable data sets too where the overhead of holes in a hash map becomes costly.

Prior art:
http://www.boost.org/doc/libs/1_54_0/doc/html/boost/container/flat_map.html

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

smallintmap is great for things like file descriptor to waiter mappings where you're fine possibly trading a little memory or insertion speed for a lot of speed on lookup.

@alexcrichton

Copy link
Copy Markdown
Member

Oh interesting! This would actually be an excellent type to use in TLS currently as well...

I'm always a fan of more cool stuff in rust, and others may want to weigh in on this as well, but this feels like it should wait for our "incubator" to manifest itself.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

I added some benchmarks and modified the code to keep the vector sorted.

Before sorting:

test bench::flatmap_find_10 ... bench: 395 ns/iter (+/- 8)
test bench::flatmap_find_50 ... bench: 1065 ns/iter (+/- 30)
test bench::flatmap_insert_10 ... bench: 2865 ns/iter (+/- 224)
test bench::flatmap_insert_50 ... bench: 37811 ns/iter (+/- 889)
test bench::hashmap_find_10 ... bench: 446 ns/iter (+/- 21)
test bench::hashmap_find_50 ... bench: 482 ns/iter (+/- 56)
test bench::hashmap_insert_10 ... bench: 4943 ns/iter (+/- 148)
test bench::hashmap_insert_50 ... bench: 34150 ns/iter (+/- 1005)

After sorting:

test bench::flatmap_find_10 ... bench: 166 ns/iter (+/- 7)
test bench::flatmap_find_50 ... bench: 218 ns/iter (+/- 20)
test bench::flatmap_insert_10 ... bench: 3161 ns/iter (+/- 251)
test bench::flatmap_insert_50 ... bench: 17743 ns/iter (+/- 696)
test bench::hashmap_find_10 ... bench: 447 ns/iter (+/- 24)
test bench::hashmap_find_50 ... bench: 480 ns/iter (+/- 23)
test bench::hashmap_insert_10 ... bench: 4956 ns/iter (+/- 277)
test bench::hashmap_insert_50 ... bench: 34484 ns/iter (+/- 5944)

@huonw

huonw commented Oct 1, 2013

Copy link
Copy Markdown
Contributor

Is it worth having the extra restriction of Ord? Someone can use treemap if they have that extra structure. (It's a trade-off between speed and generality.)

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

Good point, TotalOrd is the only trait needed. Fixed.

@huonw

huonw commented Oct 1, 2013

Copy link
Copy Markdown
Contributor

Erm, that's not a relaxation: fewer types are TotalOrd than Ord!

I actually meant this should be totally unordered, since we have other maps that cover that case. I.e. this should be a "pure" association list, which is the simplest map, and the only one that works for types that only implement Eq.

@erickt

Copy link
Copy Markdown
Contributor

@toffaletti: awesome! I kept meaning to add something like this, but never got around to it. Two comments:

  • Is FlatMap a common name for this? I've always heard of this pattern being called an association list. For example, here's OCaml implementation. Perhaps this should be named AssocMap?
  • Should we have a copyless from_owned_vec(v: ~[(K,V)]) -> FlatMap<K,V> and .into_owned_vec(self) -> ~[(K,V)]?

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

@huonw I see, I thought you just meant it was silly I had Eq+Ord+TotalOrd. I did originally implement it without ordering because I hadn't gotten around to writing the sorting code yet. Perhaps it is my lack of imagination, but I have a hard time coming up with a use case for FlatMap where the keys couldn't be ordered. An important property of FlatMap is that it is fast and the ordering is a part of that. It has a few advantages over TreeMap like less memory usage, fewer allocations, and faster lookups.

@erickt boost calls it flat_map. The wikipedia link you posted says an association list is a linked list. flat map is a sorted vector, more like an array backed binary heap.

FlatMap exploits how amazingly fast modern hardware is at accessing contiguous memory. It is memory efficient and faster than you might expect.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

I turned the benchmark code into a macro and added TreeMap, but it seems to be misbehaving. I randomly see benchmarks take 0 ns/iter. I couldn't find a bug reporting that problem, can anyone verify? I've made the imports in flatmap.rs so you can just rust test flatmap.rs --bench. Thanks for all the help and feedback everyone.

@alexcrichton

Copy link
Copy Markdown
Member

If you see 0 ns/iter it often means that your iterations are taking too long, I believe the current threshold is 1ms for the maximum amount of time your iterations should take.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

I made https://github.com/toffaletti/rust/tree/flatmap-used to test some real-world usage of FlatMap by replacing libextra usages of TreeMap with FlatMap. It exposed some issues with FlatMap that I've fixed to make stage2-check pass.

One thing worth mentioning is the changes ended up being much farther reaching than I'd hoped or expected because many things using the json module were depending on it using TreeMap to represent json::Object which seems like a code smell.

I'm not sure how to quantify this for merging, mostly it was just a fun and educational experiment for me. I think @alexcrichton is probably right saying it can wait.

@alexcrichton

Copy link
Copy Markdown
Member

As I mentioned on #9816, if we don't end up merging this, I would highly recommend putting this in a rustpkg repo on http://hiho.io/rust-ci/, because I'm sure others will find this useful!

@pnkfelixpnkfelix mentioned this pull request Oct 15, 2013
@brson

Copy link
Copy Markdown
Contributor

I too think this is a good data structure, but that we should not put it in libextra since it is soon to be dissolved and moved to various other places, and this probably doesn't belong in std. It would be nice to have a discussion of what data structures we need in std, and whether we should have a single, well-supported external package for more advanced or specialized data structures like this one.

@brson

Copy link
Copy Markdown
Contributor

Closing for the above reasons.

@brsonbrson closed this Oct 16, 2013
flip1995 pushed a commit to flip1995/rust that referenced this pull request Oct 20, 2022
fix `box-default` linting `no_std` non-boxes
This fixesrust-lang#9653 by doing the check against the `Box` type correctly even if `Box` isn't there, as in `no_std` code. Thanks to `@lukas-code` for opening the issue and supplying a reproducer!
---
changelog: none
U007D pushed a commit to U007D/rust-mos that referenced this pull request Aug 21, 2026
9653: minor: cov-mark r=Veykril a=Veykril
bors r+
Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@toffaletti@sfackler@alexcrichton@thestinger@huonw@erickt@brson@bluss
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

FlatMap, a vector map for extra - #9653

Closed
toffaletti wants to merge 1 commit into
rust-lang:masterfrom
toffaletti:flatmap
Closed

FlatMap, a vector map for extra#9653
toffaletti wants to merge 1 commit into
rust-lang:masterfrom
toffaletti:flatmap

Conversation

@toffaletti

Copy link
Copy Markdown
Contributor

I don't expect this to be merged as-is, but I'd like some eyes on what I've done so far. I'd like to implement a FlatSet in this file also to have as much feature parity with hashmap.rs as possible.

Comment threadsrc/libextra/flatmap.rs Outdated

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must be a better way to do this, but I struggled to find one.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this compile?

 match self.find_mut(&k) {
Some(val) => {
found(&k, val, a);
return val
}
None => ()
}
let v = not_found(&k, a);
self.data.push((k,v));
match self.data.mut_rev_iter().next() {
Some(&(_, ref mut val)) => val,
None => unreachable!(),
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, but I was referring to the match self.data.mut_rev_iter().next() line, wanting to find a better way to do that.

src/libextra/flatmap.rs:71:8: 71:17 error: cannot borrow `(*self).data` as mutable more than once at a time
src/libextra/flatmap.rs:71 self.data.push((k,v));
^~~~~~~~~
src/libextra/flatmap.rs:63:14: 63:19 note: second borrow of `(*self).data` as mutable occurs here
src/libextra/flatmap.rs:63 match self.find_mut(&k) {
^~~~~
src/libextra/flatmap.rs:72:14: 72:23 error: cannot borrow `(*(*self).data)[]` as mutable more than once at a time
src/libextra/flatmap.rs:72 match self.data.mut_rev_iter().next() {
^~~~~~~~~
src/libextra/flatmap.rs:63:14: 63:19 note: second borrow of `(*(*self).data)[]` as mutable occurs here
src/libextra/flatmap.rs:63 match self.find_mut(&k) {

@sfackler

Copy link
Copy Markdown
Member

extra::smallintmap should probably be deleted if this goes through.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

Maybe. It looks like smallintmap's keys are the indexes into the vector, so it can be even smaller than FlatMap under the right conditions.

@alexcrichton

Copy link
Copy Markdown
Member

This is some nice code, thanks! That being said, I'm not sure how many more containers libextra needs at the moment. This would be an excellent candidate for the "package incubator" type situation that we envision for libextra, but I'm not certain that this needs to be merged at this time.

I could be wrong though, does this have a good use case that I'm not seeing? It seems that this has O(n) on almost all operations which seems undesirable? I could be overlooking something though.

@thestinger

Copy link
Copy Markdown
Contributor

@sfackler: smallintmap has O(1) insertion, deletion and "search" - isn't this O(n)?

@sfackler

Copy link
Copy Markdown
Member

Oh yeah, nevermind.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

@alexcrichton I haven't benchmarked this implementation, but this type of container is generally faster than a hash map for small data sets like say http headers.

I'm not taking advantage of sorting yet, but if you have sorted keys this type of container can be great for large immutable data sets too where the overhead of holes in a hash map becomes costly.

Prior art:
http://www.boost.org/doc/libs/1_54_0/doc/html/boost/container/flat_map.html

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

smallintmap is great for things like file descriptor to waiter mappings where you're fine possibly trading a little memory or insertion speed for a lot of speed on lookup.

@alexcrichton

Copy link
Copy Markdown
Member

Oh interesting! This would actually be an excellent type to use in TLS currently as well...

I'm always a fan of more cool stuff in rust, and others may want to weigh in on this as well, but this feels like it should wait for our "incubator" to manifest itself.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

I added some benchmarks and modified the code to keep the vector sorted.

Before sorting:

test bench::flatmap_find_10 ... bench: 395 ns/iter (+/- 8)
test bench::flatmap_find_50 ... bench: 1065 ns/iter (+/- 30)
test bench::flatmap_insert_10 ... bench: 2865 ns/iter (+/- 224)
test bench::flatmap_insert_50 ... bench: 37811 ns/iter (+/- 889)
test bench::hashmap_find_10 ... bench: 446 ns/iter (+/- 21)
test bench::hashmap_find_50 ... bench: 482 ns/iter (+/- 56)
test bench::hashmap_insert_10 ... bench: 4943 ns/iter (+/- 148)
test bench::hashmap_insert_50 ... bench: 34150 ns/iter (+/- 1005)

After sorting:

test bench::flatmap_find_10 ... bench: 166 ns/iter (+/- 7)
test bench::flatmap_find_50 ... bench: 218 ns/iter (+/- 20)
test bench::flatmap_insert_10 ... bench: 3161 ns/iter (+/- 251)
test bench::flatmap_insert_50 ... bench: 17743 ns/iter (+/- 696)
test bench::hashmap_find_10 ... bench: 447 ns/iter (+/- 24)
test bench::hashmap_find_50 ... bench: 480 ns/iter (+/- 23)
test bench::hashmap_insert_10 ... bench: 4956 ns/iter (+/- 277)
test bench::hashmap_insert_50 ... bench: 34484 ns/iter (+/- 5944)

@huonw

huonw commented Oct 1, 2013

Copy link
Copy Markdown
Contributor

Is it worth having the extra restriction of Ord? Someone can use treemap if they have that extra structure. (It's a trade-off between speed and generality.)

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

Good point, TotalOrd is the only trait needed. Fixed.

@huonw

huonw commented Oct 1, 2013

Copy link
Copy Markdown
Contributor

Erm, that's not a relaxation: fewer types are TotalOrd than Ord!

I actually meant this should be totally unordered, since we have other maps that cover that case. I.e. this should be a "pure" association list, which is the simplest map, and the only one that works for types that only implement Eq.

@erickt

Copy link
Copy Markdown
Contributor

@toffaletti: awesome! I kept meaning to add something like this, but never got around to it. Two comments:

  • Is FlatMap a common name for this? I've always heard of this pattern being called an association list. For example, here's OCaml implementation. Perhaps this should be named AssocMap?
  • Should we have a copyless from_owned_vec(v: ~[(K,V)]) -> FlatMap<K,V> and .into_owned_vec(self) -> ~[(K,V)]?

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

@huonw I see, I thought you just meant it was silly I had Eq+Ord+TotalOrd. I did originally implement it without ordering because I hadn't gotten around to writing the sorting code yet. Perhaps it is my lack of imagination, but I have a hard time coming up with a use case for FlatMap where the keys couldn't be ordered. An important property of FlatMap is that it is fast and the ordering is a part of that. It has a few advantages over TreeMap like less memory usage, fewer allocations, and faster lookups.

@erickt boost calls it flat_map. The wikipedia link you posted says an association list is a linked list. flat map is a sorted vector, more like an array backed binary heap.

FlatMap exploits how amazingly fast modern hardware is at accessing contiguous memory. It is memory efficient and faster than you might expect.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

I turned the benchmark code into a macro and added TreeMap, but it seems to be misbehaving. I randomly see benchmarks take 0 ns/iter. I couldn't find a bug reporting that problem, can anyone verify? I've made the imports in flatmap.rs so you can just rust test flatmap.rs --bench. Thanks for all the help and feedback everyone.

@alexcrichton

Copy link
Copy Markdown
Member

If you see 0 ns/iter it often means that your iterations are taking too long, I believe the current threshold is 1ms for the maximum amount of time your iterations should take.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

I made https://github.com/toffaletti/rust/tree/flatmap-used to test some real-world usage of FlatMap by replacing libextra usages of TreeMap with FlatMap. It exposed some issues with FlatMap that I've fixed to make stage2-check pass.

One thing worth mentioning is the changes ended up being much farther reaching than I'd hoped or expected because many things using the json module were depending on it using TreeMap to represent json::Object which seems like a code smell.

I'm not sure how to quantify this for merging, mostly it was just a fun and educational experiment for me. I think @alexcrichton is probably right saying it can wait.

@alexcrichton

Copy link
Copy Markdown
Member

As I mentioned on #9816, if we don't end up merging this, I would highly recommend putting this in a rustpkg repo on http://hiho.io/rust-ci/, because I'm sure others will find this useful!

@pnkfelixpnkfelix mentioned this pull request Oct 15, 2013
@brson

Copy link
Copy Markdown
Contributor

I too think this is a good data structure, but that we should not put it in libextra since it is soon to be dissolved and moved to various other places, and this probably doesn't belong in std. It would be nice to have a discussion of what data structures we need in std, and whether we should have a single, well-supported external package for more advanced or specialized data structures like this one.

@brson

Copy link
Copy Markdown
Contributor

Closing for the above reasons.

@brsonbrson closed this Oct 16, 2013
flip1995 pushed a commit to flip1995/rust that referenced this pull request Oct 20, 2022
fix `box-default` linting `no_std` non-boxes
This fixesrust-lang#9653 by doing the check against the `Box` type correctly even if `Box` isn't there, as in `no_std` code. Thanks to `@lukas-code` for opening the issue and supplying a reproducer!
---
changelog: none
U007D pushed a commit to U007D/rust-mos that referenced this pull request Aug 21, 2026
9653: minor: cov-mark r=Veykril a=Veykril
bors r+
Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@toffaletti@sfackler@alexcrichton@thestinger@huonw@erickt@brson@bluss
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

FlatMap, a vector map for extra - #9653

Closed
toffaletti wants to merge 1 commit into
rust-lang:masterfrom
toffaletti:flatmap
Closed

FlatMap, a vector map for extra#9653
toffaletti wants to merge 1 commit into
rust-lang:masterfrom
toffaletti:flatmap

Conversation

@toffaletti

Copy link
Copy Markdown
Contributor

I don't expect this to be merged as-is, but I'd like some eyes on what I've done so far. I'd like to implement a FlatSet in this file also to have as much feature parity with hashmap.rs as possible.

Comment threadsrc/libextra/flatmap.rs Outdated

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must be a better way to do this, but I struggled to find one.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this compile?

 match self.find_mut(&k) {
Some(val) => {
found(&k, val, a);
return val
}
None => ()
}
let v = not_found(&k, a);
self.data.push((k,v));
match self.data.mut_rev_iter().next() {
Some(&(_, ref mut val)) => val,
None => unreachable!(),
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, but I was referring to the match self.data.mut_rev_iter().next() line, wanting to find a better way to do that.

src/libextra/flatmap.rs:71:8: 71:17 error: cannot borrow `(*self).data` as mutable more than once at a time
src/libextra/flatmap.rs:71 self.data.push((k,v));
^~~~~~~~~
src/libextra/flatmap.rs:63:14: 63:19 note: second borrow of `(*self).data` as mutable occurs here
src/libextra/flatmap.rs:63 match self.find_mut(&k) {
^~~~~
src/libextra/flatmap.rs:72:14: 72:23 error: cannot borrow `(*(*self).data)[]` as mutable more than once at a time
src/libextra/flatmap.rs:72 match self.data.mut_rev_iter().next() {
^~~~~~~~~
src/libextra/flatmap.rs:63:14: 63:19 note: second borrow of `(*(*self).data)[]` as mutable occurs here
src/libextra/flatmap.rs:63 match self.find_mut(&k) {

@sfackler

Copy link
Copy Markdown
Member

extra::smallintmap should probably be deleted if this goes through.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

Maybe. It looks like smallintmap's keys are the indexes into the vector, so it can be even smaller than FlatMap under the right conditions.

@alexcrichton

Copy link
Copy Markdown
Member

This is some nice code, thanks! That being said, I'm not sure how many more containers libextra needs at the moment. This would be an excellent candidate for the "package incubator" type situation that we envision for libextra, but I'm not certain that this needs to be merged at this time.

I could be wrong though, does this have a good use case that I'm not seeing? It seems that this has O(n) on almost all operations which seems undesirable? I could be overlooking something though.

@thestinger

Copy link
Copy Markdown
Contributor

@sfackler: smallintmap has O(1) insertion, deletion and "search" - isn't this O(n)?

@sfackler

Copy link
Copy Markdown
Member

Oh yeah, nevermind.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

@alexcrichton I haven't benchmarked this implementation, but this type of container is generally faster than a hash map for small data sets like say http headers.

I'm not taking advantage of sorting yet, but if you have sorted keys this type of container can be great for large immutable data sets too where the overhead of holes in a hash map becomes costly.

Prior art:
http://www.boost.org/doc/libs/1_54_0/doc/html/boost/container/flat_map.html

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

smallintmap is great for things like file descriptor to waiter mappings where you're fine possibly trading a little memory or insertion speed for a lot of speed on lookup.

@alexcrichton

Copy link
Copy Markdown
Member

Oh interesting! This would actually be an excellent type to use in TLS currently as well...

I'm always a fan of more cool stuff in rust, and others may want to weigh in on this as well, but this feels like it should wait for our "incubator" to manifest itself.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

I added some benchmarks and modified the code to keep the vector sorted.

Before sorting:

test bench::flatmap_find_10 ... bench: 395 ns/iter (+/- 8)
test bench::flatmap_find_50 ... bench: 1065 ns/iter (+/- 30)
test bench::flatmap_insert_10 ... bench: 2865 ns/iter (+/- 224)
test bench::flatmap_insert_50 ... bench: 37811 ns/iter (+/- 889)
test bench::hashmap_find_10 ... bench: 446 ns/iter (+/- 21)
test bench::hashmap_find_50 ... bench: 482 ns/iter (+/- 56)
test bench::hashmap_insert_10 ... bench: 4943 ns/iter (+/- 148)
test bench::hashmap_insert_50 ... bench: 34150 ns/iter (+/- 1005)

After sorting:

test bench::flatmap_find_10 ... bench: 166 ns/iter (+/- 7)
test bench::flatmap_find_50 ... bench: 218 ns/iter (+/- 20)
test bench::flatmap_insert_10 ... bench: 3161 ns/iter (+/- 251)
test bench::flatmap_insert_50 ... bench: 17743 ns/iter (+/- 696)
test bench::hashmap_find_10 ... bench: 447 ns/iter (+/- 24)
test bench::hashmap_find_50 ... bench: 480 ns/iter (+/- 23)
test bench::hashmap_insert_10 ... bench: 4956 ns/iter (+/- 277)
test bench::hashmap_insert_50 ... bench: 34484 ns/iter (+/- 5944)

@huonw

huonw commented Oct 1, 2013

Copy link
Copy Markdown
Contributor

Is it worth having the extra restriction of Ord? Someone can use treemap if they have that extra structure. (It's a trade-off between speed and generality.)

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

Good point, TotalOrd is the only trait needed. Fixed.

@huonw

huonw commented Oct 1, 2013

Copy link
Copy Markdown
Contributor

Erm, that's not a relaxation: fewer types are TotalOrd than Ord!

I actually meant this should be totally unordered, since we have other maps that cover that case. I.e. this should be a "pure" association list, which is the simplest map, and the only one that works for types that only implement Eq.

@erickt

Copy link
Copy Markdown
Contributor

@toffaletti: awesome! I kept meaning to add something like this, but never got around to it. Two comments:

  • Is FlatMap a common name for this? I've always heard of this pattern being called an association list. For example, here's OCaml implementation. Perhaps this should be named AssocMap?
  • Should we have a copyless from_owned_vec(v: ~[(K,V)]) -> FlatMap<K,V> and .into_owned_vec(self) -> ~[(K,V)]?

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

@huonw I see, I thought you just meant it was silly I had Eq+Ord+TotalOrd. I did originally implement it without ordering because I hadn't gotten around to writing the sorting code yet. Perhaps it is my lack of imagination, but I have a hard time coming up with a use case for FlatMap where the keys couldn't be ordered. An important property of FlatMap is that it is fast and the ordering is a part of that. It has a few advantages over TreeMap like less memory usage, fewer allocations, and faster lookups.

@erickt boost calls it flat_map. The wikipedia link you posted says an association list is a linked list. flat map is a sorted vector, more like an array backed binary heap.

FlatMap exploits how amazingly fast modern hardware is at accessing contiguous memory. It is memory efficient and faster than you might expect.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

I turned the benchmark code into a macro and added TreeMap, but it seems to be misbehaving. I randomly see benchmarks take 0 ns/iter. I couldn't find a bug reporting that problem, can anyone verify? I've made the imports in flatmap.rs so you can just rust test flatmap.rs --bench. Thanks for all the help and feedback everyone.

@alexcrichton

Copy link
Copy Markdown
Member

If you see 0 ns/iter it often means that your iterations are taking too long, I believe the current threshold is 1ms for the maximum amount of time your iterations should take.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

I made https://github.com/toffaletti/rust/tree/flatmap-used to test some real-world usage of FlatMap by replacing libextra usages of TreeMap with FlatMap. It exposed some issues with FlatMap that I've fixed to make stage2-check pass.

One thing worth mentioning is the changes ended up being much farther reaching than I'd hoped or expected because many things using the json module were depending on it using TreeMap to represent json::Object which seems like a code smell.

I'm not sure how to quantify this for merging, mostly it was just a fun and educational experiment for me. I think @alexcrichton is probably right saying it can wait.

@alexcrichton

Copy link
Copy Markdown
Member

As I mentioned on #9816, if we don't end up merging this, I would highly recommend putting this in a rustpkg repo on http://hiho.io/rust-ci/, because I'm sure others will find this useful!

@pnkfelixpnkfelix mentioned this pull request Oct 15, 2013
@brson

Copy link
Copy Markdown
Contributor

I too think this is a good data structure, but that we should not put it in libextra since it is soon to be dissolved and moved to various other places, and this probably doesn't belong in std. It would be nice to have a discussion of what data structures we need in std, and whether we should have a single, well-supported external package for more advanced or specialized data structures like this one.

@brson

Copy link
Copy Markdown
Contributor

Closing for the above reasons.

@brsonbrson closed this Oct 16, 2013
flip1995 pushed a commit to flip1995/rust that referenced this pull request Oct 20, 2022
fix `box-default` linting `no_std` non-boxes
This fixesrust-lang#9653 by doing the check against the `Box` type correctly even if `Box` isn't there, as in `no_std` code. Thanks to `@lukas-code` for opening the issue and supplying a reproducer!
---
changelog: none
U007D pushed a commit to U007D/rust-mos that referenced this pull request Aug 21, 2026
9653: minor: cov-mark r=Veykril a=Veykril
bors r+
Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@toffaletti@sfackler@alexcrichton@thestinger@huonw@erickt@brson@bluss
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

FlatMap, a vector map for extra - #9653

Closed
toffaletti wants to merge 1 commit into
rust-lang:masterfrom
toffaletti:flatmap
Closed

FlatMap, a vector map for extra#9653
toffaletti wants to merge 1 commit into
rust-lang:masterfrom
toffaletti:flatmap

Conversation

@toffaletti

Copy link
Copy Markdown
Contributor

I don't expect this to be merged as-is, but I'd like some eyes on what I've done so far. I'd like to implement a FlatSet in this file also to have as much feature parity with hashmap.rs as possible.

Comment threadsrc/libextra/flatmap.rs Outdated

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must be a better way to do this, but I struggled to find one.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this compile?

 match self.find_mut(&k) {
Some(val) => {
found(&k, val, a);
return val
}
None => ()
}
let v = not_found(&k, a);
self.data.push((k,v));
match self.data.mut_rev_iter().next() {
Some(&(_, ref mut val)) => val,
None => unreachable!(),
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, but I was referring to the match self.data.mut_rev_iter().next() line, wanting to find a better way to do that.

src/libextra/flatmap.rs:71:8: 71:17 error: cannot borrow `(*self).data` as mutable more than once at a time
src/libextra/flatmap.rs:71 self.data.push((k,v));
^~~~~~~~~
src/libextra/flatmap.rs:63:14: 63:19 note: second borrow of `(*self).data` as mutable occurs here
src/libextra/flatmap.rs:63 match self.find_mut(&k) {
^~~~~
src/libextra/flatmap.rs:72:14: 72:23 error: cannot borrow `(*(*self).data)[]` as mutable more than once at a time
src/libextra/flatmap.rs:72 match self.data.mut_rev_iter().next() {
^~~~~~~~~
src/libextra/flatmap.rs:63:14: 63:19 note: second borrow of `(*(*self).data)[]` as mutable occurs here
src/libextra/flatmap.rs:63 match self.find_mut(&k) {

@sfackler

Copy link
Copy Markdown
Member

extra::smallintmap should probably be deleted if this goes through.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

Maybe. It looks like smallintmap's keys are the indexes into the vector, so it can be even smaller than FlatMap under the right conditions.

@alexcrichton

Copy link
Copy Markdown
Member

This is some nice code, thanks! That being said, I'm not sure how many more containers libextra needs at the moment. This would be an excellent candidate for the "package incubator" type situation that we envision for libextra, but I'm not certain that this needs to be merged at this time.

I could be wrong though, does this have a good use case that I'm not seeing? It seems that this has O(n) on almost all operations which seems undesirable? I could be overlooking something though.

@thestinger

Copy link
Copy Markdown
Contributor

@sfackler: smallintmap has O(1) insertion, deletion and "search" - isn't this O(n)?

@sfackler

Copy link
Copy Markdown
Member

Oh yeah, nevermind.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

@alexcrichton I haven't benchmarked this implementation, but this type of container is generally faster than a hash map for small data sets like say http headers.

I'm not taking advantage of sorting yet, but if you have sorted keys this type of container can be great for large immutable data sets too where the overhead of holes in a hash map becomes costly.

Prior art:
http://www.boost.org/doc/libs/1_54_0/doc/html/boost/container/flat_map.html

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

smallintmap is great for things like file descriptor to waiter mappings where you're fine possibly trading a little memory or insertion speed for a lot of speed on lookup.

@alexcrichton

Copy link
Copy Markdown
Member

Oh interesting! This would actually be an excellent type to use in TLS currently as well...

I'm always a fan of more cool stuff in rust, and others may want to weigh in on this as well, but this feels like it should wait for our "incubator" to manifest itself.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

I added some benchmarks and modified the code to keep the vector sorted.

Before sorting:

test bench::flatmap_find_10 ... bench: 395 ns/iter (+/- 8)
test bench::flatmap_find_50 ... bench: 1065 ns/iter (+/- 30)
test bench::flatmap_insert_10 ... bench: 2865 ns/iter (+/- 224)
test bench::flatmap_insert_50 ... bench: 37811 ns/iter (+/- 889)
test bench::hashmap_find_10 ... bench: 446 ns/iter (+/- 21)
test bench::hashmap_find_50 ... bench: 482 ns/iter (+/- 56)
test bench::hashmap_insert_10 ... bench: 4943 ns/iter (+/- 148)
test bench::hashmap_insert_50 ... bench: 34150 ns/iter (+/- 1005)

After sorting:

test bench::flatmap_find_10 ... bench: 166 ns/iter (+/- 7)
test bench::flatmap_find_50 ... bench: 218 ns/iter (+/- 20)
test bench::flatmap_insert_10 ... bench: 3161 ns/iter (+/- 251)
test bench::flatmap_insert_50 ... bench: 17743 ns/iter (+/- 696)
test bench::hashmap_find_10 ... bench: 447 ns/iter (+/- 24)
test bench::hashmap_find_50 ... bench: 480 ns/iter (+/- 23)
test bench::hashmap_insert_10 ... bench: 4956 ns/iter (+/- 277)
test bench::hashmap_insert_50 ... bench: 34484 ns/iter (+/- 5944)

@huonw

huonw commented Oct 1, 2013

Copy link
Copy Markdown
Contributor

Is it worth having the extra restriction of Ord? Someone can use treemap if they have that extra structure. (It's a trade-off between speed and generality.)

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

Good point, TotalOrd is the only trait needed. Fixed.

@huonw

huonw commented Oct 1, 2013

Copy link
Copy Markdown
Contributor

Erm, that's not a relaxation: fewer types are TotalOrd than Ord!

I actually meant this should be totally unordered, since we have other maps that cover that case. I.e. this should be a "pure" association list, which is the simplest map, and the only one that works for types that only implement Eq.

@erickt

Copy link
Copy Markdown
Contributor

@toffaletti: awesome! I kept meaning to add something like this, but never got around to it. Two comments:

  • Is FlatMap a common name for this? I've always heard of this pattern being called an association list. For example, here's OCaml implementation. Perhaps this should be named AssocMap?
  • Should we have a copyless from_owned_vec(v: ~[(K,V)]) -> FlatMap<K,V> and .into_owned_vec(self) -> ~[(K,V)]?

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

@huonw I see, I thought you just meant it was silly I had Eq+Ord+TotalOrd. I did originally implement it without ordering because I hadn't gotten around to writing the sorting code yet. Perhaps it is my lack of imagination, but I have a hard time coming up with a use case for FlatMap where the keys couldn't be ordered. An important property of FlatMap is that it is fast and the ordering is a part of that. It has a few advantages over TreeMap like less memory usage, fewer allocations, and faster lookups.

@erickt boost calls it flat_map. The wikipedia link you posted says an association list is a linked list. flat map is a sorted vector, more like an array backed binary heap.

FlatMap exploits how amazingly fast modern hardware is at accessing contiguous memory. It is memory efficient and faster than you might expect.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

I turned the benchmark code into a macro and added TreeMap, but it seems to be misbehaving. I randomly see benchmarks take 0 ns/iter. I couldn't find a bug reporting that problem, can anyone verify? I've made the imports in flatmap.rs so you can just rust test flatmap.rs --bench. Thanks for all the help and feedback everyone.

@alexcrichton

Copy link
Copy Markdown
Member

If you see 0 ns/iter it often means that your iterations are taking too long, I believe the current threshold is 1ms for the maximum amount of time your iterations should take.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

I made https://github.com/toffaletti/rust/tree/flatmap-used to test some real-world usage of FlatMap by replacing libextra usages of TreeMap with FlatMap. It exposed some issues with FlatMap that I've fixed to make stage2-check pass.

One thing worth mentioning is the changes ended up being much farther reaching than I'd hoped or expected because many things using the json module were depending on it using TreeMap to represent json::Object which seems like a code smell.

I'm not sure how to quantify this for merging, mostly it was just a fun and educational experiment for me. I think @alexcrichton is probably right saying it can wait.

@alexcrichton

Copy link
Copy Markdown
Member

As I mentioned on #9816, if we don't end up merging this, I would highly recommend putting this in a rustpkg repo on http://hiho.io/rust-ci/, because I'm sure others will find this useful!

@pnkfelixpnkfelix mentioned this pull request Oct 15, 2013
@brson

Copy link
Copy Markdown
Contributor

I too think this is a good data structure, but that we should not put it in libextra since it is soon to be dissolved and moved to various other places, and this probably doesn't belong in std. It would be nice to have a discussion of what data structures we need in std, and whether we should have a single, well-supported external package for more advanced or specialized data structures like this one.

@brson

Copy link
Copy Markdown
Contributor

Closing for the above reasons.

@brsonbrson closed this Oct 16, 2013
flip1995 pushed a commit to flip1995/rust that referenced this pull request Oct 20, 2022
fix `box-default` linting `no_std` non-boxes
This fixesrust-lang#9653 by doing the check against the `Box` type correctly even if `Box` isn't there, as in `no_std` code. Thanks to `@lukas-code` for opening the issue and supplying a reproducer!
---
changelog: none
U007D pushed a commit to U007D/rust-mos that referenced this pull request Aug 21, 2026
9653: minor: cov-mark r=Veykril a=Veykril
bors r+
Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@toffaletti@sfackler@alexcrichton@thestinger@huonw@erickt@brson@bluss
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

FlatMap, a vector map for extra - #9653

Closed
toffaletti wants to merge 1 commit into
rust-lang:masterfrom
toffaletti:flatmap
Closed

FlatMap, a vector map for extra#9653
toffaletti wants to merge 1 commit into
rust-lang:masterfrom
toffaletti:flatmap

Conversation

@toffaletti

Copy link
Copy Markdown
Contributor

I don't expect this to be merged as-is, but I'd like some eyes on what I've done so far. I'd like to implement a FlatSet in this file also to have as much feature parity with hashmap.rs as possible.

Comment threadsrc/libextra/flatmap.rs Outdated

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must be a better way to do this, but I struggled to find one.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this compile?

 match self.find_mut(&k) {
Some(val) => {
found(&k, val, a);
return val
}
None => ()
}
let v = not_found(&k, a);
self.data.push((k,v));
match self.data.mut_rev_iter().next() {
Some(&(_, ref mut val)) => val,
None => unreachable!(),
}

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, but I was referring to the match self.data.mut_rev_iter().next() line, wanting to find a better way to do that.

src/libextra/flatmap.rs:71:8: 71:17 error: cannot borrow `(*self).data` as mutable more than once at a time
src/libextra/flatmap.rs:71 self.data.push((k,v));
^~~~~~~~~
src/libextra/flatmap.rs:63:14: 63:19 note: second borrow of `(*self).data` as mutable occurs here
src/libextra/flatmap.rs:63 match self.find_mut(&k) {
^~~~~
src/libextra/flatmap.rs:72:14: 72:23 error: cannot borrow `(*(*self).data)[]` as mutable more than once at a time
src/libextra/flatmap.rs:72 match self.data.mut_rev_iter().next() {
^~~~~~~~~
src/libextra/flatmap.rs:63:14: 63:19 note: second borrow of `(*(*self).data)[]` as mutable occurs here
src/libextra/flatmap.rs:63 match self.find_mut(&k) {

@sfackler

Copy link
Copy Markdown
Member

extra::smallintmap should probably be deleted if this goes through.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

Maybe. It looks like smallintmap's keys are the indexes into the vector, so it can be even smaller than FlatMap under the right conditions.

@alexcrichton

Copy link
Copy Markdown
Member

This is some nice code, thanks! That being said, I'm not sure how many more containers libextra needs at the moment. This would be an excellent candidate for the "package incubator" type situation that we envision for libextra, but I'm not certain that this needs to be merged at this time.

I could be wrong though, does this have a good use case that I'm not seeing? It seems that this has O(n) on almost all operations which seems undesirable? I could be overlooking something though.

@thestinger

Copy link
Copy Markdown
Contributor

@sfackler: smallintmap has O(1) insertion, deletion and "search" - isn't this O(n)?

@sfackler

Copy link
Copy Markdown
Member

Oh yeah, nevermind.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

@alexcrichton I haven't benchmarked this implementation, but this type of container is generally faster than a hash map for small data sets like say http headers.

I'm not taking advantage of sorting yet, but if you have sorted keys this type of container can be great for large immutable data sets too where the overhead of holes in a hash map becomes costly.

Prior art:
http://www.boost.org/doc/libs/1_54_0/doc/html/boost/container/flat_map.html

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

smallintmap is great for things like file descriptor to waiter mappings where you're fine possibly trading a little memory or insertion speed for a lot of speed on lookup.

@alexcrichton

Copy link
Copy Markdown
Member

Oh interesting! This would actually be an excellent type to use in TLS currently as well...

I'm always a fan of more cool stuff in rust, and others may want to weigh in on this as well, but this feels like it should wait for our "incubator" to manifest itself.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

I added some benchmarks and modified the code to keep the vector sorted.

Before sorting:

test bench::flatmap_find_10 ... bench: 395 ns/iter (+/- 8)
test bench::flatmap_find_50 ... bench: 1065 ns/iter (+/- 30)
test bench::flatmap_insert_10 ... bench: 2865 ns/iter (+/- 224)
test bench::flatmap_insert_50 ... bench: 37811 ns/iter (+/- 889)
test bench::hashmap_find_10 ... bench: 446 ns/iter (+/- 21)
test bench::hashmap_find_50 ... bench: 482 ns/iter (+/- 56)
test bench::hashmap_insert_10 ... bench: 4943 ns/iter (+/- 148)
test bench::hashmap_insert_50 ... bench: 34150 ns/iter (+/- 1005)

After sorting:

test bench::flatmap_find_10 ... bench: 166 ns/iter (+/- 7)
test bench::flatmap_find_50 ... bench: 218 ns/iter (+/- 20)
test bench::flatmap_insert_10 ... bench: 3161 ns/iter (+/- 251)
test bench::flatmap_insert_50 ... bench: 17743 ns/iter (+/- 696)
test bench::hashmap_find_10 ... bench: 447 ns/iter (+/- 24)
test bench::hashmap_find_50 ... bench: 480 ns/iter (+/- 23)
test bench::hashmap_insert_10 ... bench: 4956 ns/iter (+/- 277)
test bench::hashmap_insert_50 ... bench: 34484 ns/iter (+/- 5944)

@huonw

huonw commented Oct 1, 2013

Copy link
Copy Markdown
Contributor

Is it worth having the extra restriction of Ord? Someone can use treemap if they have that extra structure. (It's a trade-off between speed and generality.)

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

Good point, TotalOrd is the only trait needed. Fixed.

@huonw

huonw commented Oct 1, 2013

Copy link
Copy Markdown
Contributor

Erm, that's not a relaxation: fewer types are TotalOrd than Ord!

I actually meant this should be totally unordered, since we have other maps that cover that case. I.e. this should be a "pure" association list, which is the simplest map, and the only one that works for types that only implement Eq.

@erickt

Copy link
Copy Markdown
Contributor

@toffaletti: awesome! I kept meaning to add something like this, but never got around to it. Two comments:

  • Is FlatMap a common name for this? I've always heard of this pattern being called an association list. For example, here's OCaml implementation. Perhaps this should be named AssocMap?
  • Should we have a copyless from_owned_vec(v: ~[(K,V)]) -> FlatMap<K,V> and .into_owned_vec(self) -> ~[(K,V)]?

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

@huonw I see, I thought you just meant it was silly I had Eq+Ord+TotalOrd. I did originally implement it without ordering because I hadn't gotten around to writing the sorting code yet. Perhaps it is my lack of imagination, but I have a hard time coming up with a use case for FlatMap where the keys couldn't be ordered. An important property of FlatMap is that it is fast and the ordering is a part of that. It has a few advantages over TreeMap like less memory usage, fewer allocations, and faster lookups.

@erickt boost calls it flat_map. The wikipedia link you posted says an association list is a linked list. flat map is a sorted vector, more like an array backed binary heap.

FlatMap exploits how amazingly fast modern hardware is at accessing contiguous memory. It is memory efficient and faster than you might expect.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

I turned the benchmark code into a macro and added TreeMap, but it seems to be misbehaving. I randomly see benchmarks take 0 ns/iter. I couldn't find a bug reporting that problem, can anyone verify? I've made the imports in flatmap.rs so you can just rust test flatmap.rs --bench. Thanks for all the help and feedback everyone.

@alexcrichton

Copy link
Copy Markdown
Member

If you see 0 ns/iter it often means that your iterations are taking too long, I believe the current threshold is 1ms for the maximum amount of time your iterations should take.

@toffaletti

Copy link
Copy Markdown
ContributorAuthor

I made https://github.com/toffaletti/rust/tree/flatmap-used to test some real-world usage of FlatMap by replacing libextra usages of TreeMap with FlatMap. It exposed some issues with FlatMap that I've fixed to make stage2-check pass.

One thing worth mentioning is the changes ended up being much farther reaching than I'd hoped or expected because many things using the json module were depending on it using TreeMap to represent json::Object which seems like a code smell.

I'm not sure how to quantify this for merging, mostly it was just a fun and educational experiment for me. I think @alexcrichton is probably right saying it can wait.

@alexcrichton

Copy link
Copy Markdown
Member

As I mentioned on #9816, if we don't end up merging this, I would highly recommend putting this in a rustpkg repo on http://hiho.io/rust-ci/, because I'm sure others will find this useful!

@pnkfelixpnkfelix mentioned this pull request Oct 15, 2013
@brson

Copy link
Copy Markdown
Contributor

I too think this is a good data structure, but that we should not put it in libextra since it is soon to be dissolved and moved to various other places, and this probably doesn't belong in std. It would be nice to have a discussion of what data structures we need in std, and whether we should have a single, well-supported external package for more advanced or specialized data structures like this one.

@brson

Copy link
Copy Markdown
Contributor

Closing for the above reasons.

@brsonbrson closed this Oct 16, 2013
flip1995 pushed a commit to flip1995/rust that referenced this pull request Oct 20, 2022
fix `box-default` linting `no_std` non-boxes
This fixesrust-lang#9653 by doing the check against the `Box` type correctly even if `Box` isn't there, as in `no_std` code. Thanks to `@lukas-code` for opening the issue and supplying a reproducer!
---
changelog: none
U007D pushed a commit to U007D/rust-mos that referenced this pull request Aug 21, 2026
9653: minor: cov-mark r=Veykril a=Veykril
bors r+
Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants

@toffaletti@sfackler@alexcrichton@thestinger@huonw@erickt@brson@bluss