Skip to content

Conversation

arturmelanchyk
Copy link

@arturmelanchyk arturmelanchyk commented Sep 21, 2025

The PR adds support for boolset linter

boolset is a Go linter that finds map[T]bool values which are effectively used as sets and recommends switching to
map[T]struct{}.
The replacement avoids storing redundant boolean payloads and can cut memory usage for large maps in half while keeping semantics identical.

Signed-off-by: Artur Melanchyk <[email protected]>
Copy link

boring-cyborg bot commented Sep 21, 2025

Hey, thank you for opening your first Pull Request !

@CLAassistant
Copy link

CLAassistant commented Sep 21, 2025

CLA assistant check
All committers have signed the CLA.

@ldez
Copy link
Member

ldez commented Sep 21, 2025

In order for a pull request adding a linter to be reviewed, the linter and the PR must follow some requirements.

  • The CLA must be signed

Pull Request Description

  • It must have a link to the linter repository.
  • It must provide a short description of the linter.

Base Requirements

These requirements are not declarative; the team will verify them.

  • It must not be a duplicate of another linter or a rule of a linter.
  • It must not have false positives/negatives.

Linter

  • It must have a valid license:
    • AGPL is not allowed
    • The file must start with LICENSE (capitalized)
    • The file must contain the required information by the license, ex: author, year, etc.
  • It must use Go version <= 1.24.0
  • The linter repository must have a CI and tests.
  • It must use go/analysis.
  • It must have a valid semver tag, ex: v1.0.0, v0.1.0 (0.0.x are not valid).
  • It must not contain:
    • init()
    • panic()
    • log.Fatal(), os.Exit(), or similar.
  • It must not modify the AST.
  • It must have tests inside golangci-lint.

The Linter Tests Inside Golangci-lint

  • They must have at least one std lib import.
  • They must have integration tests without configuration (default).
  • They must have integration tests with configuration (if the linter has a configuration).

.golangci.next.reference.yml

  • The file .golangci.next.reference.yml must be updated.
  • The file .golangci.reference.yml must NOT be edited.
  • The linter must be added to the lists of available linters (alphabetical case-insensitive order).
    • enable and disable options
  • If the linter has a configuration, the exhaustive configuration of the linter must be added (alphabetical case-insensitive order)
    • The values must be different from the default ones.
    • The default values must be defined in a comment.
    • The option must have a short description.

Other Requirements

  • The files (tests and linter) inside golangci-lint must have the same name as the linter.
  • The .golangci.yml of golangci-lint itself must not be edited, and the linter must not be added to this file.
  • The linters must be sorted in alphabetical order (case-insensitive) in the lintersdb/builder_linter.go and .golangci.next.reference.yml.
  • The load mode (WithLoadMode(...)):
    • if the linter uses goanalysis.LoadModeSyntax -> no WithLoadForGoAnalysis() in lintersdb/builder_linter.go
    • if the linter uses goanalysis.LoadModeTypesInfo, it requires WithLoadForGoAnalysis() in lintersdb/builder_linter.go
  • The version in WithSince(...) must be the next minor version (v2.X.0) of golangci-lint.
  • WithURL() must contain the URL of the repository.

Recommendations

  • The file jsonschema/golangci.next.jsonschema.json should be updated.
  • The file jsonschema/golangci.jsonschema.json must NOT be edited.
  • The linter repository should have a readme and linting.
  • The linter should be published as a binary (useful for diagnosing bug origins).
  • The linter repository should have a .gitignore (IDE files, binaries, OS files, etc. should not be committed)
  • Use main as the default branch name.

Workflow

  • A tag should never be recreated.
  • The reviews or changes should be addressed as commits (no squash).

The golangci-lint team will edit this comment to check the boxes before and during the review.

The code review will start after the completion of those checkboxes (except for the specific items that the team will help to verify).

If the author of the PR is a member of the golangci-lint team, he should not edit this message.

This checklist does not imply that we will accept the linter.

@ldez ldez added the linter: new Support new linter label Sep 21, 2025
@ldez ldez self-requested a review September 21, 2025 21:18
@ldez ldez changed the title feat: boolset linter feat: ad boolset linter Sep 21, 2025
@ldez ldez changed the title feat: ad boolset linter feat: add boolset linter Sep 21, 2025
@ldez ldez added the waiting for: contributor feedback Requires additional feedback label Sep 21, 2025
@ldez
Copy link
Member

ldez commented Sep 21, 2025

I think that boolmap could be a better name than boolset. There is no notion of Set in Go.

IMHO, this is an "early optimization" linter: there is no problem of using map[]bool, even the Go team uses them inside Go.
It may be an improvement, but always applying this rule is a bad practice, because in most cases, readability is more important than possible optimisations in edge cases with a big map.
I, personally, will never use it.

For now, I don't know if this linter will be accepted; I need to think about it.

@arturmelanchyk
Copy link
Author

Hi @ldez, I appreciate your feedback.



In Golang there isn't a built-in set container type. Sets are just maps but don't care about element values. In Go, map[Tkey]struct{} is often used as a set type. Many Go devs prefer map[T]struct{} for sets because it



- uses zero-size values,



- encodes intent (“presence only”) instead of a tri-state (true/false/absent)
,



- can shave a bit of memory/CPU in large sets




In my opinion the only justified case to use map[T]bool is when we use both true AND false values in it. For example in this case we do want to have false value and we use it as a cache.

	workDone := make(map[string]bool)
	for _, item := range workItems {
		// can be both true and false
		workDone[item] = DoSomeWork()
	}

	for _, item := range workItems {
		if workDone[item] {
			// value successfully processed earlier
		} else {
			// calculation failed but we do not wat to recalculate
		}
	}

However this is quite rare case and my linter will not complain about such use cases.

The suggested linter warns only about usages where map is used as a set with true values only.







My motivation to add this linter is that I found many projects on the internet actually prefer this notation, see my merged PRs to Bbolt, dolt and Xray-core.






At some point I realized that having a linter for this type of issues would make greater impact than just manually fixing them.







As to the naming, boolset actually says what it does: "bool map which is effectively used as a set"

@bombsimon
Copy link
Member

I think the boolset name is fine since it is more or less representing a set. I interpret the name as something like "don't use bool, it's a set".

I agree this is a small optimization in most cases, but I also think a lot of developers aren't aware of ZST (one could say this is as close as we get in Go) and simply avoid this pattern because they're unaware of it.

This is something I use personally even for small collections just to make it clear it's a set and not a map I want to represent, a complement to good variable naming without having to put the type in the variable name. So even though the readme mentions memory and performance, I think this might be just as valid as a code style linter.

I do realize a lot of users wants to roll their own implementation and avoid dependencies, I wonder if encouraging something like deckarep/golang-set to even better represent a set would be a good/better idea.

@arturmelanchyk
Copy link
Author

arturmelanchyk commented Sep 22, 2025

as an example, here is collections.Set type that typescript team uses specifically to avoid invalid map[T]bool usages
https://github.com/microsoft/typescript-go/blob/1ca5a2d97b0c2b93cfc32137c8922185b4f41df0/internal/collections/set.go#L6

IMHO linter should just highlight the issue and not suggest using specific implementations

@ldez
Copy link
Member

ldez commented Sep 22, 2025







My motivation to add this linter is that I found many projects on the internet actually prefer this notation

This is an over-interpretation of your PRs:

  1. There are no expressions of preference inside the reviews.
  2. Some maintainers, inside those PRs, say the same thing as I about "early optimization".

I have a problem evaluating this linter as a "style" linter because, like prealloc, it will be used as a performance requirement instead of something that should be evaluated on a case-by-case basis.
In most cases, this impacts the readability and doesn't provide any improvement.

I have already closed several PRs on my various projects related to preallocating slices or maps in unnecessary cases or on other types of "early optimizations".

I still need to think about this linter.


I do realize a lot of users wants to roll their own implementation and avoid dependencies, I wonder if encouraging something like deckarep/golang-set to even better represent a set would be a good/better idea.

IMHO linter should just highlight the issue and not suggest using specific implementations

I agree that a linter should not recommend a lib, and this lib specifically should be avoided because of the dependency on the mongo-driver.

@ldez
Copy link
Member

ldez commented Sep 22, 2025

This linter is at the borderline between a detector and a linter.

Based on my analysis of several projects, this linter has false negatives (ex: map[string]map[string]bool).
And, once again, in most cases (more than 90%, based on my analysis), the reports can be evaluated as false positives (no huge maps, no concrete performance impact).
This moves the balance to the detector side.

I don't want to reproduce the prealloc situation.

No decision for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
linter: new Support new linter waiting for: contributor feedback Requires additional feedback
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants