From e6f045a38d83eaa53cc8c3f5c681f84b3489929b Mon Sep 17 00:00:00 2001
From: Adam Bryant <adam.bryant@launchwindow.io>
Date: Thu, 12 Mar 2026 09:58:51 -0700
Subject: [PATCH 1/8] chore: bump dependencies

---
 Cargo.toml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index cbda8e0..43420c7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,10 +12,10 @@ keywords = ["affinity", "thread-affinity", "cpu", "core"]
 categories = ["os"]
 
 [dependencies]
-num_cpus = "^1.14.0"
+num_cpus = "^1.17.0"
 
 [target.'cfg(any(target_os = "android", target_os = "linux", target_os = "macos", target_os = "freebsd", target_os = "netbsd"))'.dependencies]
-libc = "^0.2.30"
+libc = "^0.2.183"
 
 [target.'cfg(target_os = "windows")'.dependencies]
 winapi = { version = "^0.3.9", features = ["processthreadsapi", "winbase"] }

From ec922b89171bafdeca2107701d128b47c5fb244d Mon Sep 17 00:00:00 2001
From: Adam Bryant <adam.bryant@launchwindow.io>
Date: Thu, 12 Mar 2026 10:38:02 -0700
Subject: [PATCH 2/8] feat!: update crate ownership, split platform modules,
 add optional error API, and migrate CI

## Summary
This PR delivers a major refactor and ownership transition for the `core_affinity` fork under LaunchWindow.

### What changed
- Split the monolithic implementation into platform-specific modules:
  - Linux, Windows, macOS, FreeBSD, NetBSD, and stub fallback.
- Preserved public API compatibility:
  - `get_core_ids() -> Option<Vec<CoreId>>`
  - `set_for_current(CoreId) -> bool`
- Added optional, feature-gated error boundary:
  - `errors` feature with `thiserror`
  - `try_get_core_ids()` and `try_set_for_current()` returning typed errors.
- Improved testability and coverage:
  - Extracted private pure helpers for deterministic unit tests.
  - Added tests for conversion/error paths and Windows group-index mapping logic.
- Updated crate metadata for new stewardship:
  - new authors, version `1.0.0`, edition `2024`, rust-version `1.93.0`
  - repository/homepage/publish policy aligned for internal fork usage.
- Preserved upstream attribution metadata for original project/license continuity.
- Migrated CI from Travis/AppVeyor to GitHub Actions:
  - stable-only
  - native test runs on Linux/macOS/Windows
  - expanded Linux/Apple target compile coverage
  - no cache steps.

### Why
- Improve maintainability and platform isolation.
- Establish explicit ownership and release policy for the fork.
- Provide safer optional error handling for consumers that want typed failures.
- Replace deprecated CI providers with modern, reproducible GitHub Actions workflows.

### Validation
- `cargo test` passes.
- `cargo test --features errors` passes.
- strict clippy path passes for errors feature.
- CI workflow updated to match requested target breadth and stable-only policy.
---
 .appveyor.yml            |  44 ---
 .gitattributes           |  45 +++
 .github/workflows/ci.yml | 118 ++++++
 .gitignore               |   2 -
 .rumdl.toml              |   9 +
 .travis.yml              | 193 ----------
 .vscode/settings.json    |  13 +
 Cargo.lock               | 105 ++++++
 Cargo.toml               |  49 ++-
 README.md                | 155 ++++++--
 rust-toolchain.toml      |   4 +
 rustfmt.toml             |   5 +
 src/lib.rs               | 759 ++++++---------------------------------
 src/platform/freebsd.rs  | 134 +++++++
 src/platform/linux.rs    | 138 +++++++
 src/platform/macos.rs    |  88 +++++
 src/platform/netbsd.rs   | 103 ++++++
 src/platform/stub.rs     |  13 +
 src/platform/windows.rs  | 145 ++++++++
 19 files changed, 1200 insertions(+), 922 deletions(-)
 delete mode 100644 .appveyor.yml
 create mode 100644 .gitattributes
 create mode 100644 .github/workflows/ci.yml
 create mode 100644 .rumdl.toml
 delete mode 100644 .travis.yml
 create mode 100644 .vscode/settings.json
 create mode 100644 Cargo.lock
 create mode 100644 rust-toolchain.toml
 create mode 100644 rustfmt.toml
 create mode 100644 src/platform/freebsd.rs
 create mode 100644 src/platform/linux.rs
 create mode 100644 src/platform/macos.rs
 create mode 100644 src/platform/netbsd.rs
 create mode 100644 src/platform/stub.rs
 create mode 100644 src/platform/windows.rs

diff --git a/.appveyor.yml b/.appveyor.yml
deleted file mode 100644
index 85f568a..0000000
--- a/.appveyor.yml
+++ /dev/null
@@ -1,44 +0,0 @@
-environment:
-  matrix:
-    - TARGET: i686-pc-windows-gnu
-      CHANNEL: stable
-    - TARGET: x86_64-pc-windows-gnu
-      CHANNEL: stable
-
-    - TARGET: i686-pc-windows-gnu
-      CHANNEL: beta
-    - TARGET: x86_64-pc-windows-gnu
-      CHANNEL: beta
-
-    - TARGET: i686-pc-windows-gnu
-      CHANNEL: nightly
-    - TARGET: x86_64-pc-windows-gnu
-      CHANNEL: nightly
-
-    - TARGET: i686-pc-windows-msvc
-      CHANNEL: stable
-    - TARGET: x86_64-pc-windows-msvc
-      CHANNEL: stable
-
-    - TARGET: i686-pc-windows-msvc
-      CHANNEL: beta
-    - TARGET: x86_64-pc-windows-msvc
-      CHANNEL: beta
-
-    - TARGET: i686-pc-windows-msvc
-      CHANNEL: nightly
-    - TARGET: x86_64-pc-windows-msvc
-      CHANNEL: nightly
-
-install:
-  - curl -sSf -o rustup-init.exe https://win.rustup.rs
-  - rustup-init.exe --default-host %TARGET% --default-toolchain %CHANNEL% -y
-  - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
-  - rustc -Vv
-  - cargo -V
-
-build: false
-
-test_script:
-  - cargo build
-  - cargo test
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..56257af
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,45 @@
+###############################
+# Git Line Endings            #
+###############################
+# Set default behaviour to automatically normalize line endings.
+* text eol=lf
+# Force batch scripts to always use CRLF line endings so that if a repo is accessed
+# in Windows via a file share from Linux, the scripts will work.
+*.{cmd,[cC][mM][dD]} text eol=crlf
+*.{bat,[bB][aA][tT]} text eol=crlf
+# Force bash scripts to always use LF line endings so that if a repo is accessed
+# in Unix via a file share from Windows, the scripts will work.
+*.sh text eol=lf
+###############################
+# Git Large File System (LFS) #
+###############################
+# Archives
+*.7z filter=lfs diff=lfs merge=lfs -text
+*.br filter=lfs diff=lfs merge=lfs -text
+*.gz filter=lfs diff=lfs merge=lfs -text
+*.rar filter=lfs diff=lfs merge=lfs -text
+*.tar filter=lfs diff=lfs merge=lfs -text
+*.zip filter=lfs diff=lfs merge=lfs -text
+# Documents
+*.pdf filter=lfs diff=lfs merge=lfs -text
+# Images
+*.gif filter=lfs diff=lfs merge=lfs -text
+*.ico filter=lfs diff=lfs merge=lfs -text
+*.jpg filter=lfs diff=lfs merge=lfs -text
+*.pdf filter=lfs diff=lfs merge=lfs -text
+*.png filter=lfs diff=lfs merge=lfs -text
+*.psd filter=lfs diff=lfs merge=lfs -text
+*.webp filter=lfs diff=lfs merge=lfs -text
+# Fonts
+*.eot filter=lfs diff=lfs merge=lfs -text
+*.otf filter=lfs diff=lfs merge=lfs -text
+*.svg filter=lfs diff=lfs merge=lfs -text
+*.ttf filter=lfs diff=lfs merge=lfs -text
+*.woff filter=lfs diff=lfs merge=lfs -text
+*.woff2 filter=lfs diff=lfs merge=lfs -text
+# Other
+*.exe filter=lfs diff=lfs merge=lfs -text
+*.swf filter=lfs diff=lfs merge=lfs -text
+# Models
+*.glb filter=lfs diff=lfs merge=lfs -text
+*.icns filter=lfs diff=lfs merge=lfs -text
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..9804c03
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,118 @@
+name: CI
+
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+  workflow_dispatch:
+
+permissions:
+  contents: read
+
+env:
+  CARGO_TERM_COLOR: always
+
+jobs:
+  linux-target-check:
+    name: Linux Target Check / ${{ matrix.target }}
+    runs-on: ubuntu-24.04
+    strategy:
+      fail-fast: false
+      matrix:
+        target:
+          - i686-unknown-linux-gnu
+          - x86_64-unknown-linux-gnu
+          - arm-unknown-linux-gnueabihf
+          - aarch64-unknown-linux-gnu
+          - mips-unknown-linux-gnu
+          - i686-unknown-linux-musl
+          - x86_64-unknown-linux-musl
+          - aarch64-unknown-linux-musl
+          - i686-linux-android
+          - x86_64-linux-android
+          - arm-linux-androideabi
+          - aarch64-linux-android
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Install Rust stable with target
+        uses: dtolnay/rust-toolchain@stable
+        with:
+          toolchain: stable
+          target: ${{ matrix.target }}
+          components: clippy, rustfmt
+
+      - name: Build
+        run: cargo build --locked --target ${{ matrix.target }} --all-features --all-targets --verbose
+
+      - name: Rustfmt (check)
+        run: cargo fmt --all -- --check --config=reorder_imports=true --config=imports_granularity=Item --config=group_imports=StdExternalCrate
+
+      - name: Clippy
+        run: cargo clippy --locked --target ${{ matrix.target }} --all-features --all-targets --no-deps -- -D warnings -D clippy::pedantic
+
+  windows-target-check:
+    name: Windows Target Check / ${{ matrix.target }}
+    runs-on: windows-2022
+    strategy:
+      fail-fast: false
+      matrix:
+        target:
+          - i686-pc-windows-msvc
+          - x86_64-pc-windows-msvc
+          - aarch64-pc-windows-msvc
+          - i686-pc-windows-gnu
+          - x86_64-pc-windows-gnu
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Install Rust stable with target
+        uses: dtolnay/rust-toolchain@stable
+        with:
+          toolchain: stable
+          target: ${{ matrix.target }}
+          components: clippy, rustfmt
+
+      - name: Build
+        run: cargo build --locked --target ${{ matrix.target }} --all-features --all-targets --verbose
+
+      - name: Rustfmt (check)
+        run: cargo fmt --all -- --check --config=reorder_imports=true --config=imports_granularity=Item --config=group_imports=StdExternalCrate
+
+      - name: Clippy
+        run: cargo clippy --locked --target ${{ matrix.target }} --all-features --all-targets --no-deps -- -D warnings -D clippy::pedantic
+
+  apple-target-check:
+    name: Apple Target Check / ${{ matrix.target }}
+    runs-on: macos-14
+    strategy:
+      fail-fast: false
+      matrix:
+        target:
+          - x86_64-apple-darwin
+          - aarch64-apple-darwin
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Install Rust stable with target
+        uses: dtolnay/rust-toolchain@stable
+        with:
+          toolchain: stable
+          target: ${{ matrix.target }}
+          components: clippy, rustfmt
+
+      - name: Build
+        run: cargo build --locked --target ${{ matrix.target }} --all-features --all-targets --verbose
+
+      - name: Rustfmt (check)
+        run: cargo fmt --all -- --check --config=reorder_imports=true --config=imports_granularity=Item --config=group_imports=StdExternalCrate
+
+      - name: Clippy
+        run: cargo clippy --locked --target ${{ matrix.target }} --all-features --all-targets --no-deps -- -D warnings -D clippy::pedantic
diff --git a/.gitignore b/.gitignore
index 6aa1064..b83d222 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1 @@
 /target/
-**/*.rs.bk
-Cargo.lock
diff --git a/.rumdl.toml b/.rumdl.toml
new file mode 100644
index 0000000..83914e9
--- /dev/null
+++ b/.rumdl.toml
@@ -0,0 +1,9 @@
+[global]
+exclude = [".git", ".github", "target"]
+respect-gitignore = true
+
+[MD013]
+line-length = 120
+code-blocks = false
+tables = false
+headings = true
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 754dd59..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,193 +0,0 @@
-language: rust
-rust: stable
-
-install:
-  - if [ -z "$NO_ADD" ]; then rustup target add "$TARGET"; fi
-
-script:
-  - cargo build --verbose --target "$TARGET"
-  - if [ "$RUN" == "1" ]; then cargo test --verbose --target "$TARGET"; fi
-
-matrix:
-  include:
-      # Linux
-    #   gnu
-    #     i686
-    #       stable
-    - env: TARGET=i686-unknown-linux-gnu RUN=1
-      rust: stable
-      addons:
-        apt:
-          packages:
-            - gcc-multilib
-    #       beta
-    - env: TARGET=i686-unknown-linux-gnu RUN=1
-      rust: beta
-      addons:
-        apt:
-          packages:
-            - gcc-multilib
-    #       nightly 
-    - env: TARGET=i686-unknown-linux-gnu RUN=1
-      rust: nightly
-      addons:
-        apt:
-          packages:
-            - gcc-multilib
-    #     x86_64
-    #       stable
-    - env: TARGET=x86_64-unknown-linux-gnu RUN=1 NO_ADD=1
-      rust: stable
-    #       beta
-    - env: TARGET=x86_64-unknown-linux-gnu RUN=1 NO_ADD=1
-      rust: beta
-    #       nightly
-    - env: TARGET=x86_64-unknown-linux-gnu RUN=1 NO_ADD=1
-      rust: nightly
-    #     arm
-    #       stable
-    - env: TARGET=arm-unknown-linux-gnueabihf
-      rust: stable
-    #       beta
-    - env: TARGET=arm-unknown-linux-gnueabihf
-      rust: beta
-    #       nightly
-    - env: TARGET=arm-unknown-linux-gnueabihf
-      rust: nightly
-    #     aarch64
-    #       stable
-    - env: TARGET=aarch64-unknown-linux-gnu
-      rust: stable
-    #       beta
-    - env: TARGET=aarch64-unknown-linux-gnu
-      rust: beta
-    #       nightly
-    - env: TARGET=aarch64-unknown-linux-gnu
-      rust: nightly
-    #     mips
-    #       stable
-    - env: TARGET=mips-unknown-linux-gnu
-      rust: stable
-    #       beta
-    - env: TARGET=mips-unknown-linux-gnu
-      rust: beta
-    #       nightly
-    - env: TARGET=mips-unknown-linux-gnu
-      rust: nightly
-    #   musl
-    #     i686
-    #       stable
-    - env: TARGET=i686-unknown-linux-musl RUN=1
-      rust: stable
-      addons:
-        apt:
-          packages:
-            - gcc-multilib
-    #       beta
-    - env: TARGET=i686-unknown-linux-musl RUN=1
-      rust: beta
-      addons:
-        apt:
-          packages:
-            - gcc-multilib
-    #       nightly 
-    - env: TARGET=i686-unknown-linux-musl RUN=1
-      rust: nightly
-      addons:
-        apt:
-          packages:
-            - gcc-multilib
-    #     x86_64
-    #       stable
-    - env: TARGET=x86_64-unknown-linux-musl RUN=1
-      rust: stable
-    #       beta
-    - env: TARGET=x86_64-unknown-linux-musl RUN=1
-      rust: beta
-    #       nightly
-    - env: TARGET=x86_64-unknown-linux-musl RUN=1
-      rust: nightly
-    #     aarch64
-    #       stable
-    # - env: TARGET=aarch64-unknown-linux-musl
-    #  rust: stable
-    #       beta
-    # - env: TARGET=aarch64-unknown-linux-musl
-    #  rust: beta
-    #       nightly
-    - env: TARGET=aarch64-unknown-linux-musl
-      rust: nightly
-    # Mac OS X
-    #   i686
-    #     stable
-    - os: osx
-      env: TARGET=i686-apple-darwin RUN=1
-      rust: stable
-    #     beta
-    - os: osx
-      env: TARGET=i686-apple-darwin RUN=1
-      rust: beta
-    #     nightly
-    - os: osx
-      env: TARGET=i686-apple-darwin RUN=1
-      rust: nightly
-    #   x86_64
-    #     stable
-    - os: osx
-      env: TARGET=x86_64-apple-darwin RUN=1 NO_ADD=1
-      rust: stable 
-    #     beta
-    - os: osx
-      env: TARGET=x86_64-apple-darwin RUN=1 NO_ADD=1
-      rust: beta 
-    #     nightly
-    - os: osx
-      env: TARGET=x86_64-apple-darwin RUN=1 NO_ADD=1
-      rust: nightly 
-    # Android
-    #   i686
-    #     stable
-    - env: TARGET=i686-linux-android
-      rust: stable
-    #     beta
-    - env: TARGET=i686-linux-android
-      rust: beta
-    #     nightly
-    - env: TARGET=i686-linux-android
-      rust: nightly
-    #   x86_64
-    #     stable
-    - env: TARGET=x86_64-linux-android
-      rust: stable
-    #     beta
-    - env: TARGET=x86_64-linux-android
-      rust: beta
-    #     nightly
-    - env: TARGET=x86_64-linux-android
-      rust: nightly
-    #   arm
-    #     stable
-    - env: TARGET=arm-linux-androideabi
-      rust: stable
-    #     beta
-    - env: TARGET=arm-linux-androideabi
-      rust: beta
-    #     nightly
-    - env: TARGET=arm-linux-androideabi
-      rust: nightly
-    #   aarch64
-    #     stable
-    - env: TARGET=aarch64-linux-android
-      rust: stable
-    #     beta
-    - env: TARGET=aarch64-linux-android
-      rust: beta
-    #     nightly
-    - env: TARGET=aarch64-linux-android
-      rust: nightly
-
-
-script:
-  - cargo build
-  - cargo test
- 
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..f517774
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,13 @@
+{
+  "markdownlint.config": {
+    "MD013": {
+      "line_length": 120,
+      "code_blocks": false,
+      "tables": false,
+      "headings": true
+    }
+  },
+  "[markdown]": {
+    "editor.rulers": [120]
+  }
+}
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..1d2c6c3
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,105 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "core_affinity"
+version = "1.0.0"
+dependencies = [
+ "libc",
+ "num_cpus",
+ "thiserror",
+ "windows-sys",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
+
+[[package]]
+name = "libc"
+version = "0.2.183"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
+
+[[package]]
+name = "num_cpus"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
diff --git a/Cargo.toml b/Cargo.toml
index 43420c7..7eb9e36 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,21 +1,54 @@
 [package]
 name = "core_affinity"
-version = "0.8.3"
-authors = ["Philip Woods <elzairthesorcerer@gmail.com>"]
-description = "Manages CPU affinities"
+version = "1.0.0"
+authors = [
+    "Adam Bryant <adam.bryant@launchwindow.io>",
+    "Justin Sharma <justin.sharma@launchwindow.io>",
+]
+edition = "2024"
+rust-version = "1.93.0"
+description = "CPU affinity management for Rust threads across supported platforms"
 readme = "README.md"
 license = "MIT OR Apache-2.0"
 documentation = "https://docs.rs/core_affinity/"
-homepage = "https://github.com/Elzair/core_affinity_rs"
-repository = "https://github.com/Elzair/core_affinity_rs"
-keywords = ["affinity", "thread-affinity", "cpu", "core"]
+homepage = "https://launchwindow.io"
+repository = "https://github.com/launchwindow-software/core_affinity_rs"
+publish = false
+keywords = ["affinity", "thread-affinity", "cpu", "core", "scheduling"]
 categories = ["os"]
+include = [
+    "src/**",
+    "Cargo.toml",
+    "Cargo.lock",
+    "README.md",
+    "LICENSE-APACHE",
+    "LICENSE-MIT",
+]
+
+[package.metadata]
+maintained-by = "launchwindow-software"
+
+[package.metadata.upstream]
+crate = "core_affinity_rs"
+repository = "https://github.com/Elzair/core_affinity_rs"
+original-author = "Philip Woods <elzairthesorcerer@gmail.com>"
+license-note = "Original project copyright and license notices are retained in LICENSE-APACHE and LICENSE-MIT."
+
+[package.metadata.release]
+publish = false
+notes = "Internal fork; not published to crates.io"
+
+[features]
+default = []
+errors = ["dep:thiserror"]
 
 [dependencies]
 num_cpus = "^1.17.0"
+thiserror = { version = "^2.0.18", optional = true, default-features = false }
 
 [target.'cfg(any(target_os = "android", target_os = "linux", target_os = "macos", target_os = "freebsd", target_os = "netbsd"))'.dependencies]
 libc = "^0.2.183"
 
-[target.'cfg(target_os = "windows")'.dependencies]
-winapi = { version = "^0.3.9", features = ["processthreadsapi", "winbase"] }
+[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
+features = ["Win32_System_SystemInformation", "Win32_System_Threading"]
+version = "^0.61.2"
diff --git a/README.md b/README.md
index e1cc4ee..ca74e22 100644
--- a/README.md
+++ b/README.md
@@ -1,38 +1,145 @@
-`core_affinity_rs` is a Rust crate for managing CPU affinities. It currently supports Linux, Mac OSX, and Windows.
+# core_affinity
 
-[Documentation](https://docs.rs/core_affinity)
+Set CPU affinity for Rust threads on multiple platforms.
 
-# Example
+This repository is the LaunchWindow-maintained fork of the original
+core_affinity_rs project.
 
-This example shows how create a thread for each available processor and pin each thread to its corresponding processor. 
+The crate is currently configured as internal (`publish = false`) and is not
+published to crates.io.
+
+## Credit
+
+This work is based on the original project by Philip Woods
+<elzairthesorcerer@gmail.com>. The upstream license terms remain in effect and
+are preserved in `LICENSE-APACHE` and `LICENSE-MIT`.
+
+## Installation
+
+Because this crate is not on crates.io, use a git dependency:
+
+```toml
+[dependencies]
+core_affinity = { git = "https://github.com/launchwindow-software/core_affinity_rs" }
+```
+
+If you want typed errors, enable the `errors` feature:
+
+```toml
+[dependencies]
+core_affinity = { git = "https://github.com/launchwindow-software/core_affinity_rs", features = ["errors"] }
+```
+
+## Features
+
+- No default features (`default = []`).
+- Optional feature: `errors`.
+
+## API
+
+Default API:
+
+- `get_core_ids() -> Option<Vec<CoreId>>`
+- `set_for_current(CoreId) -> bool`
+
+Error API (`errors` feature):
+
+- `try_get_core_ids() -> Result<Vec<CoreId>, AffinityError>`
+- `try_set_for_current(CoreId) -> Result<(), AffinityError>`
+
+## Example (Default API)
 
 ```rust
-extern crate core_affinity;
+use std::thread;
+
+fn main() {
+    let Some(core_ids) = core_affinity::get_core_ids() else {
+        eprintln!("No affinity information available for this target/process");
+        return;
+    };
 
+    let handles = core_ids
+        .into_iter()
+        .map(|id| {
+            thread::spawn(move || {
+                if core_affinity::set_for_current(id) {
+                    // Do CPU-bound work here.
+                }
+            })
+        })
+        .collect::<Vec<_>>();
+
+    for handle in handles {
+        if let Err(err) = handle.join() {
+            eprintln!("thread join failed: {err:?}");
+        }
+    }
+}
+```
+
+## Example (`errors` Feature)
+
+```rust
+use core_affinity::{try_get_core_ids, try_set_for_current};
 use std::thread;
 
-// Retrieve the IDs of all cores on which the current
-// thread is allowed to run.
-// NOTE: If you want ALL the possible cores, you should
-// use num_cpus.
-let core_ids = core_affinity::get_core_ids().unwrap();
-
-// Create a thread for each active CPU core.
-let handles = core_ids.into_iter().map(|id| {
-    thread::spawn(move || {
-        // Pin this thread to a single CPU core.
-        let res = core_affinity::set_for_current(id);
-        if (res) {
-          // Do more work after this.
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+    let core_ids = try_get_core_ids()?;
+
+    let handles = core_ids
+        .into_iter()
+        .map(|id| {
+            thread::spawn(move || {
+                if let Err(err) = try_set_for_current(id) {
+                    eprintln!("failed to pin thread: {err}");
+                    return;
+                }
+
+                // Do CPU-bound work here.
+            })
+        })
+        .collect::<Vec<_>>();
+
+    for handle in handles {
+        if let Err(err) = handle.join() {
+            eprintln!("thread join failed: {err:?}");
         }
-    })
-}).collect::<Vec<_>>();
+    }
 
-for handle in handles.into_iter() {
-    handle.join().unwrap();
+    Ok(())
 }
 ```
 
-# Platforms
+## Supported Platforms
 
-`core_affinity_rs` should work on Linux, Windows, Mac OSX, FreeBSD, NetBSD, and Android.
+- Linux
+- Android
+- Windows
+- macOS
+- FreeBSD
+- NetBSD
+
+On unsupported targets:
+
+- `get_core_ids()` returns `None`
+- `set_for_current(...)` returns `false`
+
+## CI
+
+GitHub Actions validates:
+
+- Native build/test on Linux, Windows, and macOS (stable)
+- Cross-target test compilation for Linux and Apple target sets
+- Clippy checks
+
+## Development
+
+Useful commands:
+
+```bash
+cargo test
+cargo test --features errors
+cargo clippy --all-targets --features errors --no-default-features -- -D warnings
+cargo fmt --all -- --config=reorder_imports=true --config=imports_granularity=Item --config=group_imports=StdExternalCrate
+cargo clippy --fix --all-targets --allow-dirty --allow-staged --no-default-features --no-deps --keep-going -- -D clippy::pedantic
+```
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
new file mode 100644
index 0000000..54f7e7b
--- /dev/null
+++ b/rust-toolchain.toml
@@ -0,0 +1,4 @@
+[toolchain]
+channel = "1.93.0"
+components = ["rustfmt", "clippy"]
+profile = "minimal"
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 100644
index 0000000..b642953
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1,5 @@
+fn_params_layout = "Tall"
+merge_derives = true
+style_edition = "2024"
+use_field_init_shorthand = true
+use_small_heuristics = "Default"
diff --git a/src/lib.rs b/src/lib.rs
index cf8532f..146f221 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,4 @@
-//! This crate manages CPU affinities.
+//! Cross-platform CPU affinity helpers for Rust threads.
 //!
 //! ## Example
 //!
@@ -28,23 +28,39 @@
 //! }
 //! ```
 
-#[cfg(any(
-    target_os = "android",
-    target_os = "linux",
-    target_os = "macos",
-    target_os = "freebsd",
-    target_os = "netbsd"
-))]
-extern crate libc;
+#[cfg(feature = "errors")]
+/// Crate result type used by the optional `errors` feature.
+pub type Result<T> = std::result::Result<T, AffinityError>;
 
-#[cfg_attr(all(not(test), not(target_os = "macos")), allow(unused_extern_crates))]
-extern crate num_cpus;
+#[cfg(feature = "errors")]
+/// Error values returned by the feature-gated `try_*` APIs.
+#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
+pub enum AffinityError {
+    /// Core id discovery failed for the current runtime/platform context.
+    #[error("failed to retrieve available core IDs")]
+    GetCoreIdsFailed,
+    /// Thread affinity could not be applied to the requested core id.
+    #[error("failed to set affinity for core ID {core_id}")]
+    SetForCurrentFailed { core_id: usize },
+}
 
 /// This function tries to retrieve information
-/// on all the "cores" on which the current thread 
+/// on all the "cores" on which the current thread
 /// is allowed to run.
+#[must_use]
 pub fn get_core_ids() -> Option<Vec<CoreId>> {
-    get_core_ids_helper()
+    platform::get_core_ids()
+}
+
+#[cfg(feature = "errors")]
+/// Returns all core IDs available to the current thread/process affinity scope.
+///
+/// # Errors
+///
+/// Returns [`AffinityError::GetCoreIdsFailed`] when core IDs cannot be retrieved
+/// for the current platform or process context.
+pub fn try_get_core_ids() -> Result<Vec<CoreId>> {
+    core_ids_to_result(get_core_ids())
 }
 
 /// This function tries to pin the current
@@ -52,648 +68,66 @@ pub fn get_core_ids() -> Option<Vec<CoreId>> {
 ///
 /// # Arguments
 ///
-/// * core_id - ID of the core to pin
+/// * `core_id` - ID of the core to pin
+#[must_use]
 pub fn set_for_current(core_id: CoreId) -> bool {
-    set_for_current_helper(core_id)
-}
-
-/// This represents a CPU core.
-#[repr(transparent)]
-#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub struct CoreId {
-    pub id: usize,
+    platform::set_for_current(core_id)
 }
 
-// Linux Section
-
-#[cfg(any(target_os = "android", target_os = "linux"))]
-#[inline]
-fn get_core_ids_helper() -> Option<Vec<CoreId>> {
-    linux::get_core_ids()
+#[cfg(feature = "errors")]
+/// Pins the current thread to a specific core.
+///
+/// # Errors
+///
+/// Returns [`AffinityError::SetForCurrentFailed`] when setting thread affinity
+/// fails for the supplied [`CoreId`].
+pub fn try_set_for_current(core_id: CoreId) -> Result<()> {
+    set_for_current_to_result(core_id, set_for_current(core_id))
 }
 
-#[cfg(any(target_os = "android", target_os = "linux"))]
-#[inline]
-fn set_for_current_helper(core_id: CoreId) -> bool {
-    linux::set_for_current(core_id)
+#[cfg(feature = "errors")]
+fn core_ids_to_result(core_ids: Option<Vec<CoreId>>) -> Result<Vec<CoreId>> {
+    core_ids.ok_or(AffinityError::GetCoreIdsFailed)
 }
 
-#[cfg(any(target_os = "android", target_os = "linux"))]
-mod linux {
-    use std::mem;
-
-    use libc::{CPU_ISSET, CPU_SET, CPU_SETSIZE, cpu_set_t, sched_getaffinity, sched_setaffinity};
-
-    use super::CoreId;
-
-    pub fn get_core_ids() -> Option<Vec<CoreId>> {
-        if let Some(full_set) = get_affinity_mask() {
-            let mut core_ids: Vec<CoreId> = Vec::new();
-
-            for i in 0..CPU_SETSIZE as usize {
-                if unsafe { CPU_ISSET(i, &full_set) } {
-                    core_ids.push(CoreId{ id: i });
-                }
-            }
-
-            Some(core_ids)
-        }
-        else {
-            None
-        }
+#[cfg(feature = "errors")]
+fn set_for_current_to_result(core_id: CoreId, success: bool) -> Result<()> {
+    if success {
+        Ok(())
+    } else {
+        Err(AffinityError::SetForCurrentFailed {
+            core_id: core_id.id,
+        })
     }
-
-    pub fn set_for_current(core_id: CoreId) -> bool {
-        // Turn `core_id` into a `libc::cpu_set_t` with only
-        // one core active.
-        let mut set = new_cpu_set();
-
-        unsafe { CPU_SET(core_id.id, &mut set) };
-
-        // Set the current thread's core affinity.
-        let res = unsafe {
-            sched_setaffinity(0, // Defaults to current thread
-                              mem::size_of::<cpu_set_t>(),
-                              &set)
-        };
-        res == 0
-    }
-
-    fn get_affinity_mask() -> Option<cpu_set_t> {
-        let mut set = new_cpu_set();
-
-        // Try to get current core affinity mask.
-        let result = unsafe {
-            sched_getaffinity(0, // Defaults to current thread
-                              mem::size_of::<cpu_set_t>(),
-                              &mut set)
-        };
-
-        if result == 0 {
-            Some(set)
-        }
-        else {
-            None
-        }
-    }
-
-    fn new_cpu_set() -> cpu_set_t {
-        unsafe { mem::zeroed::<cpu_set_t>() }
-    }
-
-    #[cfg(test)]
-    mod tests {
-        use num_cpus;
-
-        use super::*;
-
-        #[test]
-        fn test_linux_get_affinity_mask() {
-            match get_affinity_mask() {
-                Some(_) => {},
-                None => { assert!(false); },
-            }
-        }
-
-        #[test]
-        fn test_linux_get_core_ids() {
-            match get_core_ids() {
-                Some(set) => {
-                    assert_eq!(set.len(), num_cpus::get());
-                },
-                None => { assert!(false); },
-            }
-        }
-
-        #[test]
-        fn test_linux_set_for_current() {
-            let ids = get_core_ids().unwrap();
-
-            assert!(ids.len() > 0);
-
-            let res = set_for_current(ids[0]);
-            assert_eq!(res, true);
-
-            // Ensure that the system pinned the current thread
-            // to the specified core.
-            let mut core_mask = new_cpu_set();
-            unsafe { CPU_SET(ids[0].id, &mut core_mask) };
-
-            let new_mask = get_affinity_mask().unwrap();
-
-            let mut is_equal = true;
-
-            for i in 0..CPU_SETSIZE as usize {
-                let is_set1 = unsafe {
-                    CPU_ISSET(i, &core_mask)
-                };
-                let is_set2 = unsafe {
-                    CPU_ISSET(i, &new_mask)
-                };
-
-                if is_set1 != is_set2 {
-                    is_equal = false;
-                }
-            }
-
-            assert!(is_equal);
-        }
-     }
 }
 
-// Windows Section
-
-#[cfg(target_os = "windows")]
-#[inline]
-fn get_core_ids_helper() -> Option<Vec<CoreId>> {
-    windows::get_core_ids()
-}
-
-#[cfg(target_os = "windows")]
-#[inline]
-fn set_for_current_helper(core_id: CoreId) -> bool {
-    windows::set_for_current(core_id)
+/// This represents a CPU core.
+#[repr(transparent)]
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct CoreId {
+    /// Zero-based logical core index understood by this crate.
+    pub id: usize,
 }
 
-#[cfg(target_os = "windows")]
-extern crate winapi;
+#[cfg(any(target_os = "android", target_os = "linux"))]
+#[path = "platform/linux.rs"]
+mod platform;
 
 #[cfg(target_os = "windows")]
-mod windows {
-    use winapi::shared::basetsd::{DWORD_PTR, PDWORD_PTR};
-    use winapi::um::processthreadsapi::{GetCurrentProcess, GetCurrentThread};
-    use winapi::um::winbase::{GetProcessAffinityMask, SetThreadAffinityMask};
-
-    use super::CoreId;
-
-    pub fn get_core_ids() -> Option<Vec<CoreId>> {
-        if let Some(mask) = get_affinity_mask() {
-            // Find all active cores in the bitmask.
-            let mut core_ids: Vec<CoreId> = Vec::new();
-
-            for i in 0..64 as u64 {
-                let test_mask = 1 << i;
-
-                if (mask & test_mask) == test_mask {
-                    core_ids.push(CoreId { id: i as usize });
-                }
-            }
-
-            Some(core_ids)
-        }
-        else {
-            None
-        }
-    }
-
-    pub fn set_for_current(core_id: CoreId) -> bool {
-        // Convert `CoreId` back into mask.
-        let mask: u64 = 1 << core_id.id;
-
-        // Set core affinity for current thread.
-        let res = unsafe {
-            SetThreadAffinityMask(
-                GetCurrentThread(),
-                mask as DWORD_PTR
-            )
-        };
-        res != 0
-    }
-
-    fn get_affinity_mask() -> Option<u64> {
-        let mut system_mask: usize = 0;
-        let mut process_mask: usize = 0;
-
-        let res = unsafe {
-            GetProcessAffinityMask(
-                GetCurrentProcess(),
-                &mut process_mask as PDWORD_PTR,
-                &mut system_mask as PDWORD_PTR
-            )
-        };
-
-        // Successfully retrieved affinity mask
-        if res != 0 {
-            Some(process_mask as u64)
-        }
-        // Failed to retrieve affinity mask
-        else {
-            None
-        }
-    }
-
-    #[cfg(test)]
-    mod tests {
-        use num_cpus;
-
-        use super::*;
-
-        #[test]
-        fn test_windows_get_core_ids() {
-            match get_core_ids() {
-                Some(set) => {
-                    assert_eq!(set.len(), num_cpus::get());
-                },
-                None => { assert!(false); },
-            }
-        }
-
-        #[test]
-        fn test_windows_set_for_current() {
-            let ids = get_core_ids().unwrap();
-
-            assert!(ids.len() > 0);
-
-            assert_ne!(set_for_current(ids[0]), 0);
-        }
-    }
-}
-
-// MacOS Section
-
-#[cfg(target_os = "macos")]
-#[inline]
-fn get_core_ids_helper() -> Option<Vec<CoreId>> {
-    macos::get_core_ids()
-}
-
-#[cfg(target_os = "macos")]
-#[inline]
-fn set_for_current_helper(core_id: CoreId) -> bool {
-    macos::set_for_current(core_id)
-}
+#[path = "platform/windows.rs"]
+mod platform;
 
 #[cfg(target_os = "macos")]
-mod macos {
-    use std::mem;
-
-    use libc::{c_int, c_uint, c_void, pthread_self};
-
-    use num_cpus;
-
-    use super::CoreId;
-
-    type kern_return_t = c_int;
-    type integer_t = c_int;
-    type natural_t = c_uint;
-    type thread_t = c_uint;
-    type thread_policy_flavor_t = natural_t;
-    type mach_msg_type_number_t = natural_t;
-
-    #[repr(C)]
-    struct thread_affinity_policy_data_t {
-        affinity_tag: integer_t,
-    }
-
-    type thread_policy_t = *mut thread_affinity_policy_data_t;
-
-    const THREAD_AFFINITY_POLICY: thread_policy_flavor_t = 4;
-
-    extern {
-        fn thread_policy_set(
-            thread: thread_t,
-            flavor: thread_policy_flavor_t,
-            policy_info: thread_policy_t,
-            count: mach_msg_type_number_t,
-        ) -> kern_return_t;
-    }
-
-    pub fn get_core_ids() -> Option<Vec<CoreId>> {
-        Some((0..(num_cpus::get())).into_iter()
-             .map(|n| CoreId { id: n as usize })
-             .collect::<Vec<_>>())
-    }
-
-    pub fn set_for_current(core_id: CoreId) -> bool {
-        let THREAD_AFFINITY_POLICY_COUNT: mach_msg_type_number_t =
-            mem::size_of::<thread_affinity_policy_data_t>() as mach_msg_type_number_t /
-            mem::size_of::<integer_t>() as mach_msg_type_number_t;
-
-        let mut info = thread_affinity_policy_data_t {
-            affinity_tag: core_id.id as integer_t,
-        };
-
-        let res = unsafe {
-            thread_policy_set(
-                pthread_self() as thread_t,
-                THREAD_AFFINITY_POLICY,
-                &mut info as thread_policy_t,
-                THREAD_AFFINITY_POLICY_COUNT
-            )
-        };
-        res == 0
-    }
-
-    #[cfg(test)]
-    mod tests {
-        use num_cpus;
-
-        use super::*;
-
-        #[test]
-        fn test_macos_get_core_ids() {
-            match get_core_ids() {
-                Some(set) => {
-                    assert_eq!(set.len(), num_cpus::get());
-                },
-                None => { assert!(false); },
-            }
-        }
-
-        #[test]
-        fn test_macos_set_for_current() {
-            let ids = get_core_ids().unwrap();
-            assert!(ids.len() > 0);
-            assert!(set_for_current(ids[0]))
-        }
-    }
-}
-
-
-// FreeBSD Section
+#[path = "platform/macos.rs"]
+mod platform;
 
 #[cfg(target_os = "freebsd")]
-#[inline]
-fn get_core_ids_helper() -> Option<Vec<CoreId>> {
-    freebsd::get_core_ids()
-}
-
-#[cfg(target_os = "freebsd")]
-#[inline]
-fn set_for_current_helper(core_id: CoreId) -> bool {
-    freebsd::set_for_current(core_id)
-}
-
-#[cfg(target_os = "freebsd")]
-mod freebsd {
-    use std::mem;
-
-    use libc::{
-        cpuset_getaffinity, cpuset_setaffinity, cpuset_t, CPU_ISSET,
-        CPU_LEVEL_WHICH, CPU_SET, CPU_SETSIZE, CPU_WHICH_TID,
-    };
-
-    use super::CoreId;
-
-    pub fn get_core_ids() -> Option<Vec<CoreId>> {
-        if let Some(full_set) = get_affinity_mask() {
-            let mut core_ids: Vec<CoreId> = Vec::new();
-
-            for i in 0..CPU_SETSIZE as usize {
-                if unsafe { CPU_ISSET(i, &full_set) } {
-                    core_ids.push(CoreId { id: i });
-                }
-            }
-
-            Some(core_ids)
-        } else {
-            None
-        }
-    }
-
-    pub fn set_for_current(core_id: CoreId) -> bool {
-        // Turn `core_id` into a `libc::cpuset_t` with only
-        // one core active.
-        let mut set = new_cpu_set();
-
-        unsafe { CPU_SET(core_id.id, &mut set) };
-
-        // Set the current thread's core affinity.
-        let res = unsafe {
-            // FreeBSD's sched_setaffinity currently operates on process id,
-            // therefore using cpuset_setaffinity instead.
-            cpuset_setaffinity(
-                CPU_LEVEL_WHICH,
-                CPU_WHICH_TID,
-                -1, // -1 == current thread
-                mem::size_of::<cpuset_t>(),
-                &set,
-            )
-        };
-        res == 0
-    }
-
-    fn get_affinity_mask() -> Option<cpuset_t> {
-        let mut set = new_cpu_set();
-
-        // Try to get current core affinity mask.
-        let result = unsafe {
-            // FreeBSD's sched_getaffinity currently operates on process id,
-            // therefore using cpuset_getaffinity instead.
-            cpuset_getaffinity(
-                CPU_LEVEL_WHICH,
-                CPU_WHICH_TID,
-                -1, // -1 == current thread
-                mem::size_of::<cpuset_t>(),
-                &mut set,
-            )
-        };
-
-        if result == 0 {
-            Some(set)
-        } else {
-            None
-        }
-    }
-
-    fn new_cpu_set() -> cpuset_t {
-        unsafe { mem::zeroed::<cpuset_t>() }
-    }
-
-    #[cfg(test)]
-    mod tests {
-        use num_cpus;
-
-        use super::*;
-
-        #[test]
-        fn test_freebsd_get_affinity_mask() {
-            match get_affinity_mask() {
-                Some(_) => {}
-                None => {
-                    assert!(false);
-                }
-            }
-        }
-
-        #[test]
-        fn test_freebsd_get_core_ids() {
-            match get_core_ids() {
-                Some(set) => {
-                    assert_eq!(set.len(), num_cpus::get());
-                }
-                None => {
-                    assert!(false);
-                }
-            }
-        }
-
-        #[test]
-        fn test_freebsd_set_for_current() {
-            let ids = get_core_ids().unwrap();
-
-            assert!(ids.len() > 0);
-
-            let res = set_for_current(ids[0]);
-            assert_eq!(res, true);
-
-            // Ensure that the system pinned the current thread
-            // to the specified core.
-            let mut core_mask = new_cpu_set();
-            unsafe { CPU_SET(ids[0].id, &mut core_mask) };
-
-            let new_mask = get_affinity_mask().unwrap();
-
-            let mut is_equal = true;
-
-            for i in 0..CPU_SETSIZE as usize {
-                let is_set1 = unsafe { CPU_ISSET(i, &core_mask) };
-                let is_set2 = unsafe { CPU_ISSET(i, &new_mask) };
-
-                if is_set1 != is_set2 {
-                    is_equal = false;
-                }
-            }
-
-            assert!(is_equal);
-        }
-    }
-}
-
-// NetBSD Section
-
-#[cfg(target_os = "netbsd")]
-#[inline]
-fn get_core_ids_helper() -> Option<Vec<CoreId>> {
-    netbsd::get_core_ids()
-}
+#[path = "platform/freebsd.rs"]
+mod platform;
 
 #[cfg(target_os = "netbsd")]
-#[inline]
-fn set_for_current_helper(core_id: CoreId) -> bool {
-    netbsd::set_for_current(core_id)
-}
-
-#[cfg(target_os = "netbsd")]
-mod netbsd {
-    use libc::{
-        pthread_getaffinity_np, pthread_setaffinity_np, pthread_self, cpuset_t,
-        _cpuset_create, _cpuset_size, _cpuset_set, _cpuset_isset, _cpuset_destroy
-    };
-    use num_cpus;
-
-    use super::CoreId;
-
-    pub fn get_core_ids() -> Option<Vec<CoreId>> {
-        if let Some(full_set) = get_affinity_mask() {
-            let mut core_ids: Vec<CoreId> = Vec::new();
-
-            let num_cpus = num_cpus::get();
-            for i in 0..num_cpus {
-                if unsafe { _cpuset_isset(i as u64, full_set) } >= 0 {
-                    core_ids.push(CoreId { id: i });
-                }
-            }
-            unsafe { _cpuset_destroy(full_set) };
-            Some(core_ids)
-        } else {
-            None
-        }
-    }
-
-    pub fn set_for_current(core_id: CoreId) -> bool {
-        let set = unsafe { _cpuset_create() };
-        unsafe { _cpuset_set(core_id.id as u64, set) } ;
-
-        let result = unsafe {
-            pthread_setaffinity_np(pthread_self(), _cpuset_size(set), set)
-        };
-        unsafe { _cpuset_destroy(set) };
-
-        match result {
-            0 => true,
-            _ => false,
-        }
-    }
-
-    fn get_affinity_mask() -> Option<*mut cpuset_t> {
-        let set = unsafe { _cpuset_create() };
-
-        match unsafe {
-            pthread_getaffinity_np(pthread_self(), _cpuset_size(set), set)
-        } {
-            0 => Some(set),
-            _ => None,
-        }
-    }
-
-    #[cfg(test)]
-    mod tests {
-        use num_cpus;
-
-        use super::*;
-
-        #[test]
-        fn test_netbsd_get_affinity_mask() {
-            match get_affinity_mask() {
-                Some(set) => unsafe { _cpuset_destroy(set); },
-                None => {
-                    assert!(false);
-                }
-            }
-        }
-
-        #[test]
-        fn test_netbsd_get_core_ids() {
-            match get_core_ids() {
-                Some(set) => {
-                    assert_eq!(set.len(), num_cpus::get());
-                }
-                None => {
-                    assert!(false);
-                }
-            }
-        }
-
-        #[test]
-        fn test_netbsd_set_for_current() {
-            let ids = get_core_ids().unwrap();
-
-            assert!(ids.len() > 0);
-
-            let ci = ids[ids.len() - 1]; // use the last reported core
-            let res = set_for_current(ci);
-            assert_eq!(res, true);
-
-            // Ensure that the system pinned the current thread
-            // to the specified core.
-            let new_mask = get_affinity_mask().unwrap();
-            assert!(unsafe { _cpuset_isset(ci.id as u64, new_mask) > 0 });
-            let num_cpus = num_cpus::get();
-            for i in 0..num_cpus {
-                if i != ci.id {
-                    assert_eq!(0, unsafe { _cpuset_isset(i as u64, new_mask) });
-                }
-            }
-            unsafe { _cpuset_destroy(new_mask) };
-        }
-    }
-}
-
-// Stub Section
-
-#[cfg(not(any(
-    target_os = "linux",
-    target_os = "android",
-    target_os = "windows",
-    target_os = "macos",
-    target_os = "freebsd",
-    target_os = "netbsd"
-)))]
-#[inline]
-fn get_core_ids_helper() -> Option<Vec<CoreId>> {
-    None
-}
+#[path = "platform/netbsd.rs"]
+mod platform;
 
 #[cfg(not(any(
     target_os = "linux",
@@ -703,37 +137,60 @@ fn get_core_ids_helper() -> Option<Vec<CoreId>> {
     target_os = "freebsd",
     target_os = "netbsd"
 )))]
-#[inline]
-fn set_for_current_helper(_core_id: CoreId) -> bool {
-    false
-}
+#[path = "platform/stub.rs"]
+mod platform;
 
 #[cfg(test)]
 mod tests {
-    use num_cpus;
-
     use super::*;
 
-    // #[test]
-    // fn test_num_cpus() {
-    //     println!("Num CPUs: {}", num_cpus::get());
-    //     println!("Num Physical CPUs: {}", num_cpus::get_physical());
-    // }
-
     #[test]
     fn test_get_core_ids() {
         match get_core_ids() {
             Some(set) => {
                 assert_eq!(set.len(), num_cpus::get());
-            },
-            None => { assert!(false); },
+            }
+            None => {
+                panic!("get_core_ids() returned None");
+            }
         }
     }
 
     #[test]
     fn test_set_for_current() {
         let ids = get_core_ids().unwrap();
-        assert!(ids.len() > 0);
-        assert!(set_for_current(ids[0]))
+        assert!(!ids.is_empty());
+        assert!(set_for_current(ids[0]));
+    }
+
+    #[cfg(feature = "errors")]
+    #[test]
+    fn test_core_ids_to_result_success() {
+        let ids = vec![CoreId { id: 0 }];
+        let res = core_ids_to_result(Some(ids.clone()));
+        assert_eq!(res, Ok(ids));
+    }
+
+    #[cfg(feature = "errors")]
+    #[test]
+    fn test_core_ids_to_result_error() {
+        let res = core_ids_to_result(None);
+        assert_eq!(res, Err(AffinityError::GetCoreIdsFailed));
+    }
+
+    #[cfg(feature = "errors")]
+    #[test]
+    fn test_set_for_current_to_result_success() {
+        let core_id = CoreId { id: 3 };
+        let res = set_for_current_to_result(core_id, true);
+        assert_eq!(res, Ok(()));
+    }
+
+    #[cfg(feature = "errors")]
+    #[test]
+    fn test_set_for_current_to_result_error() {
+        let core_id = CoreId { id: 7 };
+        let res = set_for_current_to_result(core_id, false);
+        assert_eq!(res, Err(AffinityError::SetForCurrentFailed { core_id: 7 }));
     }
 }
diff --git a/src/platform/freebsd.rs b/src/platform/freebsd.rs
new file mode 100644
index 0000000..89c376c
--- /dev/null
+++ b/src/platform/freebsd.rs
@@ -0,0 +1,134 @@
+//! FreeBSD CPU affinity implementation.
+
+use std::mem;
+
+use libc::CPU_ISSET;
+use libc::CPU_LEVEL_WHICH;
+use libc::CPU_SET;
+use libc::CPU_SETSIZE;
+use libc::CPU_WHICH_TID;
+use libc::cpuset_getaffinity;
+use libc::cpuset_setaffinity;
+use libc::cpuset_t;
+
+use super::CoreId;
+
+/// Returns core ids available to the current thread's FreeBSD affinity mask.
+pub fn get_core_ids() -> Option<Vec<CoreId>> {
+    if let Some(full_set) = get_affinity_mask() {
+        let mut core_ids: Vec<CoreId> = Vec::new();
+
+        for i in 0..CPU_SETSIZE as usize {
+            if unsafe { CPU_ISSET(i, &full_set) } {
+                core_ids.push(CoreId { id: i });
+            }
+        }
+
+        Some(core_ids)
+    } else {
+        None
+    }
+}
+
+/// Attempts to pin the current thread to the given FreeBSD core id.
+pub fn set_for_current(core_id: CoreId) -> bool {
+    // Turn `core_id` into a `libc::cpuset_t` with only
+    // one core active.
+    let mut set = new_cpu_set();
+
+    unsafe { CPU_SET(core_id.id, &mut set) };
+
+    // Set the current thread's core affinity.
+    let res = unsafe {
+        // FreeBSD's sched_setaffinity currently operates on process id,
+        // therefore using cpuset_setaffinity instead.
+        cpuset_setaffinity(
+            CPU_LEVEL_WHICH,
+            CPU_WHICH_TID,
+            -1, // -1 == current thread
+            mem::size_of::<cpuset_t>(),
+            &raw const set,
+        )
+    };
+    res == 0
+}
+
+fn get_affinity_mask() -> Option<cpuset_t> {
+    let mut set = new_cpu_set();
+
+    // Try to get current core affinity mask.
+    let result = unsafe {
+        // FreeBSD's sched_getaffinity currently operates on process id,
+        // therefore using cpuset_getaffinity instead.
+        cpuset_getaffinity(
+            CPU_LEVEL_WHICH,
+            CPU_WHICH_TID,
+            -1, // -1 == current thread
+            mem::size_of::<cpuset_t>(),
+            &raw mut set,
+        )
+    };
+
+    if result == 0 { Some(set) } else { None }
+}
+
+fn new_cpu_set() -> cpuset_t {
+    unsafe { mem::zeroed::<cpuset_t>() }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_freebsd_get_affinity_mask() {
+        match get_affinity_mask() {
+            Some(_) => {}
+            None => {
+                panic!("freebsd::get_affinity_mask() returned None");
+            }
+        }
+    }
+
+    #[test]
+    fn test_freebsd_get_core_ids() {
+        match get_core_ids() {
+            Some(set) => {
+                assert_eq!(set.len(), num_cpus::get());
+            }
+            None => {
+                panic!("freebsd::get_core_ids() returned None");
+            }
+        }
+    }
+
+    #[test]
+    fn test_freebsd_set_for_current() {
+        let ids = get_core_ids().unwrap();
+
+        assert!(!ids.is_empty());
+
+        let res = set_for_current(ids[0]);
+        assert!(res);
+
+        // Ensure that the system pinned the current thread
+        // to the specified core.
+        let mut core_mask = new_cpu_set();
+        unsafe { CPU_SET(ids[0].id, &mut core_mask) };
+
+        let new_mask = get_affinity_mask().unwrap();
+
+        let mut is_equal = true;
+
+        for i in 0..CPU_SETSIZE as usize {
+            let is_set1 = unsafe { CPU_ISSET(i, &core_mask) };
+            let is_set2 = unsafe { CPU_ISSET(i, &new_mask) };
+
+            if is_set1 != is_set2 {
+                is_equal = false;
+            }
+        }
+
+        assert!(is_equal);
+    }
+}
diff --git a/src/platform/linux.rs b/src/platform/linux.rs
new file mode 100644
index 0000000..8ecb40f
--- /dev/null
+++ b/src/platform/linux.rs
@@ -0,0 +1,138 @@
+//! Linux and Android CPU affinity implementation.
+
+use std::mem;
+
+use libc::CPU_ISSET;
+use libc::CPU_SET;
+use libc::CPU_SETSIZE;
+use libc::cpu_set_t;
+use libc::sched_getaffinity;
+use libc::sched_setaffinity;
+
+use super::CoreId;
+
+/// Returns core ids available to the current thread's Linux affinity mask.
+pub fn get_core_ids() -> Option<Vec<CoreId>> {
+    if let Some(full_set) = get_affinity_mask() {
+        let cpu_set_size = cpu_set_size();
+        let mut core_ids: Vec<CoreId> = Vec::new();
+
+        for i in 0..cpu_set_size {
+            if unsafe { CPU_ISSET(i, &full_set) } {
+                core_ids.push(CoreId { id: i });
+            }
+        }
+
+        Some(core_ids)
+    } else {
+        None
+    }
+}
+
+/// Attempts to pin the current thread to the given Linux core id.
+pub fn set_for_current(core_id: CoreId) -> bool {
+    // Turn `core_id` into a `libc::cpu_set_t` with only
+    // one core active.
+    let mut set = new_cpu_set();
+
+    unsafe { CPU_SET(core_id.id, &mut set) };
+
+    // Set the current thread's core affinity.
+    let res = unsafe {
+        sched_setaffinity(
+            0, // Defaults to current thread
+            mem::size_of::<cpu_set_t>(),
+            &raw const set,
+        )
+    };
+    res == 0
+}
+
+fn get_affinity_mask() -> Option<cpu_set_t> {
+    let mut set = new_cpu_set();
+
+    // Try to get current core affinity mask.
+    let result = unsafe {
+        sched_getaffinity(
+            0, // Defaults to current thread
+            mem::size_of::<cpu_set_t>(),
+            &raw mut set,
+        )
+    };
+
+    if result == 0 { Some(set) } else { None }
+}
+
+fn new_cpu_set() -> cpu_set_t {
+    unsafe { mem::zeroed::<cpu_set_t>() }
+}
+
+fn cpu_set_size() -> usize {
+    #[cfg(target_os = "android")]
+    {
+        CPU_SETSIZE
+    }
+
+    #[cfg(not(target_os = "android"))]
+    {
+        usize::try_from(CPU_SETSIZE).unwrap_or_default()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_linux_get_affinity_mask() {
+        match get_affinity_mask() {
+            Some(_) => {}
+            None => {
+                panic!("linux::get_affinity_mask() returned None");
+            }
+        }
+    }
+
+    #[test]
+    fn test_linux_get_core_ids() {
+        match get_core_ids() {
+            Some(set) => {
+                assert_eq!(set.len(), num_cpus::get());
+            }
+            None => {
+                panic!("linux::get_core_ids() returned None");
+            }
+        }
+    }
+
+    #[test]
+    fn test_linux_set_for_current() {
+        let ids = get_core_ids().unwrap();
+
+        assert!(!ids.is_empty());
+
+        let res = set_for_current(ids[0]);
+        assert!(res);
+
+        // Ensure that the system pinned the current thread
+        // to the specified core.
+        let mut core_mask = new_cpu_set();
+        unsafe { CPU_SET(ids[0].id, &mut core_mask) };
+
+        let new_mask = get_affinity_mask().unwrap();
+
+        let mut is_equal = true;
+
+        let cpu_set_size = cpu_set_size();
+        for i in 0..cpu_set_size {
+            let is_set1 = unsafe { CPU_ISSET(i, &core_mask) };
+            let is_set2 = unsafe { CPU_ISSET(i, &new_mask) };
+
+            if is_set1 != is_set2 {
+                is_equal = false;
+            }
+        }
+
+        assert!(is_equal);
+    }
+}
diff --git a/src/platform/macos.rs b/src/platform/macos.rs
new file mode 100644
index 0000000..1c76f8f
--- /dev/null
+++ b/src/platform/macos.rs
@@ -0,0 +1,88 @@
+//! macOS CPU affinity implementation.
+
+use std::mem;
+
+use libc::c_int;
+use libc::c_uint;
+use libc::pthread_self;
+
+use super::CoreId;
+
+type KernReturnT = c_int;
+type IntegerT = c_int;
+type NaturalT = c_uint;
+type ThreadT = c_uint;
+type ThreadPolicyFlavorT = NaturalT;
+type MachMsgTypeNumberT = NaturalT;
+
+#[repr(C)]
+struct ThreadAffinityPolicyDataT {
+    affinity_tag: IntegerT,
+}
+
+type ThreadPolicyT = *mut ThreadAffinityPolicyDataT;
+
+const THREAD_AFFINITY_POLICY: ThreadPolicyFlavorT = 4;
+
+extern "C" {
+    fn thread_policy_set(
+        thread: ThreadT,
+        flavor: ThreadPolicyFlavorT,
+        policy_info: ThreadPolicyT,
+        count: MachMsgTypeNumberT,
+    ) -> KernReturnT;
+}
+
+/// Returns core ids available on macOS.
+pub fn get_core_ids() -> Option<Vec<CoreId>> {
+    Some(
+        (0..num_cpus::get())
+            .map(|n| CoreId { id: n as usize })
+            .collect::<Vec<_>>(),
+    )
+}
+
+/// Attempts to pin the current thread to the provided macOS core id.
+pub fn set_for_current(core_id: CoreId) -> bool {
+    let thread_affinity_policy_count: MachMsgTypeNumberT =
+        mem::size_of::<ThreadAffinityPolicyDataT>() as MachMsgTypeNumberT
+            / mem::size_of::<IntegerT>() as MachMsgTypeNumberT;
+
+    let mut info = ThreadAffinityPolicyDataT {
+        affinity_tag: core_id.id as IntegerT,
+    };
+
+    let res = unsafe {
+        thread_policy_set(
+            pthread_self() as ThreadT,
+            THREAD_AFFINITY_POLICY,
+            &mut info as ThreadPolicyT,
+            thread_affinity_policy_count,
+        )
+    };
+    res == 0
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_macos_get_core_ids() {
+        match get_core_ids() {
+            Some(set) => {
+                assert_eq!(set.len(), num_cpus::get());
+            }
+            None => {
+                panic!("macos::get_core_ids() returned None");
+            }
+        }
+    }
+
+    #[test]
+    fn test_macos_set_for_current() {
+        let ids = get_core_ids().unwrap();
+        assert!(!ids.is_empty());
+        assert!(set_for_current(ids[0]))
+    }
+}
diff --git a/src/platform/netbsd.rs b/src/platform/netbsd.rs
new file mode 100644
index 0000000..4749b3d
--- /dev/null
+++ b/src/platform/netbsd.rs
@@ -0,0 +1,103 @@
+//! NetBSD CPU affinity implementation.
+
+use libc::_cpuset_create;
+use libc::_cpuset_destroy;
+use libc::_cpuset_isset;
+use libc::_cpuset_set;
+use libc::_cpuset_size;
+use libc::cpuset_t;
+use libc::pthread_getaffinity_np;
+use libc::pthread_self;
+use libc::pthread_setaffinity_np;
+
+use super::CoreId;
+
+/// Returns core ids available to the current thread on NetBSD.
+pub fn get_core_ids() -> Option<Vec<CoreId>> {
+    if let Some(full_set) = get_affinity_mask() {
+        let mut core_ids: Vec<CoreId> = Vec::new();
+
+        let num_cpus = num_cpus::get();
+        for i in 0..num_cpus {
+            if unsafe { _cpuset_isset(i as u64, full_set) } >= 0 {
+                core_ids.push(CoreId { id: i });
+            }
+        }
+        unsafe { _cpuset_destroy(full_set) };
+        Some(core_ids)
+    } else {
+        None
+    }
+}
+
+/// Attempts to pin the current thread to the given NetBSD core id.
+pub fn set_for_current(core_id: CoreId) -> bool {
+    let set = unsafe { _cpuset_create() };
+    unsafe { _cpuset_set(core_id.id as u64, set) };
+
+    let result = unsafe { pthread_setaffinity_np(pthread_self(), _cpuset_size(set), set) };
+    unsafe { _cpuset_destroy(set) };
+
+    result == 0
+}
+
+fn get_affinity_mask() -> Option<*mut cpuset_t> {
+    let set = unsafe { _cpuset_create() };
+
+    match unsafe { pthread_getaffinity_np(pthread_self(), _cpuset_size(set), set) } {
+        0 => Some(set),
+        _ => None,
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_netbsd_get_affinity_mask() {
+        match get_affinity_mask() {
+            Some(set) => unsafe {
+                _cpuset_destroy(set);
+            },
+            None => {
+                panic!("netbsd::get_affinity_mask() returned None");
+            }
+        }
+    }
+
+    #[test]
+    fn test_netbsd_get_core_ids() {
+        match get_core_ids() {
+            Some(set) => {
+                assert_eq!(set.len(), num_cpus::get());
+            }
+            None => {
+                panic!("netbsd::get_core_ids() returned None");
+            }
+        }
+    }
+
+    #[test]
+    fn test_netbsd_set_for_current() {
+        let ids = get_core_ids().unwrap();
+
+        assert!(!ids.is_empty());
+
+        let ci = ids[ids.len() - 1]; // use the last reported core
+        let res = set_for_current(ci);
+        assert!(res);
+
+        // Ensure that the system pinned the current thread
+        // to the specified core.
+        let new_mask = get_affinity_mask().unwrap();
+        assert!(unsafe { _cpuset_isset(ci.id as u64, new_mask) > 0 });
+        let num_cpus = num_cpus::get();
+        for i in 0..num_cpus {
+            if i != ci.id {
+                assert_eq!(0, unsafe { _cpuset_isset(i as u64, new_mask) });
+            }
+        }
+        unsafe { _cpuset_destroy(new_mask) };
+    }
+}
diff --git a/src/platform/stub.rs b/src/platform/stub.rs
new file mode 100644
index 0000000..3702438
--- /dev/null
+++ b/src/platform/stub.rs
@@ -0,0 +1,13 @@
+//! Fallback implementation for unsupported targets.
+
+use super::CoreId;
+
+/// Stub implementation for unsupported targets; always returns `None`.
+pub fn get_core_ids() -> Option<Vec<CoreId>> {
+    None
+}
+
+/// Stub implementation for unsupported targets; always returns `false`.
+pub fn set_for_current(_core_id: CoreId) -> bool {
+    false
+}
diff --git a/src/platform/windows.rs b/src/platform/windows.rs
new file mode 100644
index 0000000..294e03d
--- /dev/null
+++ b/src/platform/windows.rs
@@ -0,0 +1,145 @@
+//! Windows CPU affinity implementation.
+
+use std::convert::TryFrom;
+use std::ptr;
+
+use windows_sys::Win32::System::SystemInformation::GROUP_AFFINITY;
+use windows_sys::Win32::System::Threading::GetActiveProcessorCount;
+use windows_sys::Win32::System::Threading::GetActiveProcessorGroupCount;
+use windows_sys::Win32::System::Threading::GetCurrentThread;
+use windows_sys::Win32::System::Threading::SetThreadAffinityMask;
+use windows_sys::Win32::System::Threading::SetThreadGroupAffinity;
+
+use super::CoreId;
+
+#[allow(clippy::unnecessary_wraps)]
+/// Returns core ids visible to the current process on Windows.
+pub fn get_core_ids() -> Option<Vec<CoreId>> {
+    let core_count: u32 = if let Some(group_list) = unsafe { get_group_list() } {
+        let count = group_list
+            .iter()
+            .copied()
+            .map(|group| unsafe { GetActiveProcessorCount(group) })
+            .sum();
+        if count > 0 {
+            count
+        } else {
+            fallback_core_count_u32()
+        }
+    } else {
+        fallback_core_count_u32()
+    };
+
+    let core_ids: Vec<CoreId> = (0..core_count).map(|n| CoreId { id: n as usize }).collect();
+
+    Some(core_ids)
+}
+
+fn fallback_core_count_u32() -> u32 {
+    u32::try_from(num_cpus::get()).unwrap_or(u32::MAX)
+}
+
+/// Attempts to pin the current thread to the provided Windows core id.
+pub fn set_for_current(core_id: CoreId) -> bool {
+    if let Some(group_list) = unsafe { get_group_list() } {
+        let group_counts: Vec<(u16, usize)> = group_list
+            .iter()
+            .copied()
+            .map(|group| (group, unsafe { GetActiveProcessorCount(group) } as usize))
+            .collect();
+
+        if let Some((group, local_index)) = resolve_group_and_local_index(core_id.id, &group_counts)
+        {
+            let affinity = GROUP_AFFINITY {
+                Mask: 1 << local_index,
+                Group: group,
+                ..Default::default()
+            };
+            return unsafe {
+                SetThreadGroupAffinity(GetCurrentThread(), &raw const affinity, ptr::null_mut())
+                    != 0
+            };
+        }
+    }
+
+    // Fallback for environments where processor groups are unavailable.
+    if core_id.id < usize::BITS as usize {
+        let mask = 1usize << core_id.id;
+        return unsafe { SetThreadAffinityMask(GetCurrentThread(), mask) != 0 };
+    }
+
+    false
+}
+
+fn resolve_group_and_local_index(
+    global_core_id: usize,
+    group_counts: &[(u16, usize)],
+) -> Option<(u16, usize)> {
+    let mut remaining = global_core_id;
+    for (group, count) in group_counts.iter().copied() {
+        if remaining < count {
+            return Some((group, remaining));
+        }
+        remaining = remaining.saturating_sub(count);
+    }
+    None
+}
+
+unsafe fn get_group_list() -> Option<Vec<u16>> {
+    unsafe {
+        let group_count = GetActiveProcessorGroupCount();
+        if group_count == 0 {
+            return None;
+        }
+
+        let group_list: Vec<u16> = (0..group_count).collect();
+        Some(group_list)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+
+    use super::*;
+
+    #[test]
+    fn test_windows_get_core_ids() {
+        match get_core_ids() {
+            Some(set) => {
+                assert_eq!(set.len(), num_cpus::get());
+            }
+            None => {
+                panic!("windows::get_core_ids() returned None");
+            }
+        }
+    }
+
+    #[test]
+    fn test_windows_set_for_current() {
+        let ids = get_core_ids().unwrap();
+
+        assert!(!ids.is_empty());
+
+        assert!(set_for_current(ids[0]));
+    }
+
+    #[test]
+    fn test_resolve_group_and_local_index_single_group() {
+        let groups = vec![(0u16, 8usize)];
+        assert_eq!(resolve_group_and_local_index(0, &groups), Some((0, 0)));
+        assert_eq!(resolve_group_and_local_index(7, &groups), Some((0, 7)));
+        assert_eq!(resolve_group_and_local_index(8, &groups), None);
+    }
+
+    #[test]
+    fn test_resolve_group_and_local_index_multi_group() {
+        let groups = vec![(0u16, 4usize), (1u16, 4usize), (2u16, 2usize)];
+        assert_eq!(resolve_group_and_local_index(0, &groups), Some((0, 0)));
+        assert_eq!(resolve_group_and_local_index(3, &groups), Some((0, 3)));
+        assert_eq!(resolve_group_and_local_index(4, &groups), Some((1, 0)));
+        assert_eq!(resolve_group_and_local_index(7, &groups), Some((1, 3)));
+        assert_eq!(resolve_group_and_local_index(8, &groups), Some((2, 0)));
+        assert_eq!(resolve_group_and_local_index(9, &groups), Some((2, 1)));
+        assert_eq!(resolve_group_and_local_index(10, &groups), None);
+    }
+}

From 3c671a5362d8faec195fda74ef014c0057dcab2e Mon Sep 17 00:00:00 2001
From: Adam Bryant <adam.bryant@launchwindow.io>
Date: Thu, 12 Mar 2026 11:22:14 -0700
Subject: [PATCH 3/8] nits

---
 .github/workflows/ci.yml | 12 +++---------
 src/platform/macos.rs    |  2 +-
 2 files changed, 4 insertions(+), 10 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9804c03..4d8075e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -38,12 +38,10 @@ jobs:
       - name: Checkout
         uses: actions/checkout@v4
 
-      - name: Install Rust stable with target
+      - name: Install toolchain with target
         uses: dtolnay/rust-toolchain@stable
         with:
-          toolchain: stable
           target: ${{ matrix.target }}
-          components: clippy, rustfmt
 
       - name: Build
         run: cargo build --locked --target ${{ matrix.target }} --all-features --all-targets --verbose
@@ -71,12 +69,10 @@ jobs:
       - name: Checkout
         uses: actions/checkout@v4
 
-      - name: Install Rust stable with target
+      - name: Install toolchain with target
         uses: dtolnay/rust-toolchain@stable
         with:
-          toolchain: stable
           target: ${{ matrix.target }}
-          components: clippy, rustfmt
 
       - name: Build
         run: cargo build --locked --target ${{ matrix.target }} --all-features --all-targets --verbose
@@ -101,12 +97,10 @@ jobs:
       - name: Checkout
         uses: actions/checkout@v4
 
-      - name: Install Rust stable with target
+      - name: Install toolchain with target
         uses: dtolnay/rust-toolchain@stable
         with:
-          toolchain: stable
           target: ${{ matrix.target }}
-          components: clippy, rustfmt
 
       - name: Build
         run: cargo build --locked --target ${{ matrix.target }} --all-features --all-targets --verbose
diff --git a/src/platform/macos.rs b/src/platform/macos.rs
index 1c76f8f..8587514 100644
--- a/src/platform/macos.rs
+++ b/src/platform/macos.rs
@@ -24,7 +24,7 @@ type ThreadPolicyT = *mut ThreadAffinityPolicyDataT;
 
 const THREAD_AFFINITY_POLICY: ThreadPolicyFlavorT = 4;
 
-extern "C" {
+unsafe extern "C" {
     fn thread_policy_set(
         thread: ThreadT,
         flavor: ThreadPolicyFlavorT,

From 3afe03e87fc65a0b37006eff0e2411cb5a4e7830 Mon Sep 17 00:00:00 2001
From: Adam Bryant <adam.bryant@launchwindow.io>
Date: Thu, 12 Mar 2026 11:24:47 -0700
Subject: [PATCH 4/8] nit

---
 .github/workflows/ci.yml | 18 ++++++++++++------
 1 file changed, 12 insertions(+), 6 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4d8075e..9ce993e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -38,10 +38,12 @@ jobs:
       - name: Checkout
         uses: actions/checkout@v4
 
-      - name: Install toolchain with target
-        uses: dtolnay/rust-toolchain@stable
+      - name: Setup Rust
+        uses: actions-rust-lang/setup-rust-toolchain@v1.15.3
         with:
+          cache: false
           target: ${{ matrix.target }}
+          rustflags: ""
 
       - name: Build
         run: cargo build --locked --target ${{ matrix.target }} --all-features --all-targets --verbose
@@ -69,10 +71,12 @@ jobs:
       - name: Checkout
         uses: actions/checkout@v4
 
-      - name: Install toolchain with target
-        uses: dtolnay/rust-toolchain@stable
+      - name: Setup Rust
+        uses: actions-rust-lang/setup-rust-toolchain@v1.15.3
         with:
+          cache: false
           target: ${{ matrix.target }}
+          rustflags: ""
 
       - name: Build
         run: cargo build --locked --target ${{ matrix.target }} --all-features --all-targets --verbose
@@ -97,10 +101,12 @@ jobs:
       - name: Checkout
         uses: actions/checkout@v4
 
-      - name: Install toolchain with target
-        uses: dtolnay/rust-toolchain@stable
+      - name: Setup Rust
+        uses: actions-rust-lang/setup-rust-toolchain@v1.15.3
         with:
+          cache: false
           target: ${{ matrix.target }}
+          rustflags: ""
 
       - name: Build
         run: cargo build --locked --target ${{ matrix.target }} --all-features --all-targets --verbose

From 9e377de2631972e6787c1cee1e4331dfb8dbb26b Mon Sep 17 00:00:00 2001
From: Adam Bryant <adam.bryant@launchwindow.io>
Date: Thu, 12 Mar 2026 11:49:00 -0700
Subject: [PATCH 5/8] nit

---
 .github/workflows/ci.yml | 13 ++++++-------
 src/platform/macos.rs    | 31 +++++++++++++++++++++++--------
 2 files changed, 29 insertions(+), 15 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9ce993e..08bac8a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -25,7 +25,6 @@ jobs:
           - x86_64-unknown-linux-gnu
           - arm-unknown-linux-gnueabihf
           - aarch64-unknown-linux-gnu
-          - mips-unknown-linux-gnu
           - i686-unknown-linux-musl
           - x86_64-unknown-linux-musl
           - aarch64-unknown-linux-musl
@@ -46,13 +45,13 @@ jobs:
           rustflags: ""
 
       - name: Build
-        run: cargo build --locked --target ${{ matrix.target }} --all-features --all-targets --verbose
+        run: cargo build --locked --target ${{ matrix.target }} --all-features --verbose
 
       - name: Rustfmt (check)
         run: cargo fmt --all -- --check --config=reorder_imports=true --config=imports_granularity=Item --config=group_imports=StdExternalCrate
 
       - name: Clippy
-        run: cargo clippy --locked --target ${{ matrix.target }} --all-features --all-targets --no-deps -- -D warnings -D clippy::pedantic
+        run: cargo clippy --locked --target ${{ matrix.target }} --all-features --no-deps -- -D warnings -D clippy::pedantic
 
   windows-target-check:
     name: Windows Target Check / ${{ matrix.target }}
@@ -79,13 +78,13 @@ jobs:
           rustflags: ""
 
       - name: Build
-        run: cargo build --locked --target ${{ matrix.target }} --all-features --all-targets --verbose
+        run: cargo build --locked --target ${{ matrix.target }} --all-features --verbose
 
       - name: Rustfmt (check)
         run: cargo fmt --all -- --check --config=reorder_imports=true --config=imports_granularity=Item --config=group_imports=StdExternalCrate
 
       - name: Clippy
-        run: cargo clippy --locked --target ${{ matrix.target }} --all-features --all-targets --no-deps -- -D warnings -D clippy::pedantic
+        run: cargo clippy --locked --target ${{ matrix.target }} --all-features --no-deps -- -D warnings -D clippy::pedantic
 
   apple-target-check:
     name: Apple Target Check / ${{ matrix.target }}
@@ -109,10 +108,10 @@ jobs:
           rustflags: ""
 
       - name: Build
-        run: cargo build --locked --target ${{ matrix.target }} --all-features --all-targets --verbose
+        run: cargo build --locked --target ${{ matrix.target }} --all-features --verbose
 
       - name: Rustfmt (check)
         run: cargo fmt --all -- --check --config=reorder_imports=true --config=imports_granularity=Item --config=group_imports=StdExternalCrate
 
       - name: Clippy
-        run: cargo clippy --locked --target ${{ matrix.target }} --all-features --all-targets --no-deps -- -D warnings -D clippy::pedantic
+        run: cargo clippy --locked --target ${{ matrix.target }} --all-features --no-deps -- -D warnings -D clippy::pedantic
diff --git a/src/platform/macos.rs b/src/platform/macos.rs
index 8587514..043bc62 100644
--- a/src/platform/macos.rs
+++ b/src/platform/macos.rs
@@ -34,27 +34,42 @@ unsafe extern "C" {
 }
 
 /// Returns core ids available on macOS.
+#[allow(clippy::unnecessary_wraps)]
 pub fn get_core_ids() -> Option<Vec<CoreId>> {
     Some(
         (0..num_cpus::get())
-            .map(|n| CoreId { id: n as usize })
+            .map(|n| CoreId { id: n })
             .collect::<Vec<_>>(),
     )
 }
 
 /// Attempts to pin the current thread to the provided macOS core id.
 pub fn set_for_current(core_id: CoreId) -> bool {
-    let thread_affinity_policy_count: MachMsgTypeNumberT =
-        mem::size_of::<ThreadAffinityPolicyDataT>() as MachMsgTypeNumberT
-            / mem::size_of::<IntegerT>() as MachMsgTypeNumberT;
+    let policy_size = match MachMsgTypeNumberT::try_from(mem::size_of::<ThreadAffinityPolicyDataT>())
+    {
+        Ok(value) => value,
+        Err(_) => return false,
+    };
+    let integer_size = match MachMsgTypeNumberT::try_from(mem::size_of::<IntegerT>()) {
+        Ok(value) if value != 0 => value,
+        _ => return false,
+    };
+    let thread_affinity_policy_count = policy_size / integer_size;
 
-    let mut info = ThreadAffinityPolicyDataT {
-        affinity_tag: core_id.id as IntegerT,
+    let affinity_tag = match IntegerT::try_from(core_id.id) {
+        Ok(value) => value,
+        Err(_) => return false,
     };
+    let thread = match ThreadT::try_from(pthread_self()) {
+        Ok(value) => value,
+        Err(_) => return false,
+    };
+
+    let mut info = ThreadAffinityPolicyDataT { affinity_tag };
 
     let res = unsafe {
         thread_policy_set(
-            pthread_self() as ThreadT,
+            thread,
             THREAD_AFFINITY_POLICY,
             &mut info as ThreadPolicyT,
             thread_affinity_policy_count,
@@ -83,6 +98,6 @@ mod tests {
     fn test_macos_set_for_current() {
         let ids = get_core_ids().unwrap();
         assert!(!ids.is_empty());
-        assert!(set_for_current(ids[0]))
+        assert!(set_for_current(ids[0]));
     }
 }

From 7d334511a4008eb90050fd1efd8d97cb0fef0c89 Mon Sep 17 00:00:00 2001
From: Adam Bryant <adam.bryant@launchwindow.io>
Date: Thu, 12 Mar 2026 11:51:22 -0700
Subject: [PATCH 6/8] nit

---
 src/platform/macos.rs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/platform/macos.rs b/src/platform/macos.rs
index 043bc62..6ec5b1d 100644
--- a/src/platform/macos.rs
+++ b/src/platform/macos.rs
@@ -60,7 +60,8 @@ pub fn set_for_current(core_id: CoreId) -> bool {
         Ok(value) => value,
         Err(_) => return false,
     };
-    let thread = match ThreadT::try_from(pthread_self()) {
+    let pthread = unsafe { pthread_self() };
+    let thread = match ThreadT::try_from(pthread) {
         Ok(value) => value,
         Err(_) => return false,
     };

From 22eb34bc0a368354841e618543e85fb83b8dd5e7 Mon Sep 17 00:00:00 2001
From: Adam Bryant <adam.bryant@launchwindow.io>
Date: Thu, 12 Mar 2026 11:54:39 -0700
Subject: [PATCH 7/8] nit

---
 src/platform/macos.rs | 17 +++++++----------
 1 file changed, 7 insertions(+), 10 deletions(-)

diff --git a/src/platform/macos.rs b/src/platform/macos.rs
index 6ec5b1d..6adaafe 100644
--- a/src/platform/macos.rs
+++ b/src/platform/macos.rs
@@ -45,10 +45,9 @@ pub fn get_core_ids() -> Option<Vec<CoreId>> {
 
 /// Attempts to pin the current thread to the provided macOS core id.
 pub fn set_for_current(core_id: CoreId) -> bool {
-    let policy_size = match MachMsgTypeNumberT::try_from(mem::size_of::<ThreadAffinityPolicyDataT>())
-    {
-        Ok(value) => value,
-        Err(_) => return false,
+    let Ok(policy_size) = MachMsgTypeNumberT::try_from(mem::size_of::<ThreadAffinityPolicyDataT>())
+    else {
+        return false;
     };
     let integer_size = match MachMsgTypeNumberT::try_from(mem::size_of::<IntegerT>()) {
         Ok(value) if value != 0 => value,
@@ -56,14 +55,12 @@ pub fn set_for_current(core_id: CoreId) -> bool {
     };
     let thread_affinity_policy_count = policy_size / integer_size;
 
-    let affinity_tag = match IntegerT::try_from(core_id.id) {
-        Ok(value) => value,
-        Err(_) => return false,
+    let Ok(affinity_tag) = IntegerT::try_from(core_id.id) else {
+        return false;
     };
     let pthread = unsafe { pthread_self() };
-    let thread = match ThreadT::try_from(pthread) {
-        Ok(value) => value,
-        Err(_) => return false,
+    let Ok(thread) = ThreadT::try_from(pthread) else {
+        return false;
     };
 
     let mut info = ThreadAffinityPolicyDataT { affinity_tag };

From 91924b8a14947aea7b82344e0052009d439b8421 Mon Sep 17 00:00:00 2001
From: Adam Bryant <adam.bryant@launchwindow.io>
Date: Thu, 12 Mar 2026 11:59:34 -0700
Subject: [PATCH 8/8] lower minimum rust version to 1.85

---
 Cargo.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Cargo.toml b/Cargo.toml
index 7eb9e36..1f169a8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,7 +6,7 @@ authors = [
     "Justin Sharma <justin.sharma@launchwindow.io>",
 ]
 edition = "2024"
-rust-version = "1.93.0"
+rust-version = "1.85.0"
 description = "CPU affinity management for Rust threads across supported platforms"
 readme = "README.md"
 license = "MIT OR Apache-2.0"
