From c8fe6be9c932dec1a5b32c73500c4ec9e3f6ffe6 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 3 Aug 2026 10:00:19 +0200 Subject: [PATCH 1/3] Update to Rust 1.95 and sync lints with the egui repo Bump MSRV and toolchain to 1.95, and enable the workspace lints that are enabled in the main egui repo but were missing here. `clippy::ref_option` required changing `&Option>` to `Option<&LabelFormatterFn<'_>>` in `PlotItem::on_hover`, which means `LabelFormatterFn` is now public. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/rust.yml | 10 +++++----- Cargo.toml | 19 +++++++++++++++++-- clippy.toml | 2 +- egui_plot/src/items/bar_chart.rs | 4 ++-- egui_plot/src/items/box_plot.rs | 4 ++-- egui_plot/src/items/heatmap.rs | 4 ++-- egui_plot/src/items/mod.rs | 6 +++--- egui_plot/src/label.rs | 3 ++- egui_plot/src/lib.rs | 1 + egui_plot/src/plot.rs | 10 +++++----- rust-toolchain | 2 +- scripts/check.sh | 2 +- scripts/clippy_wasm/clippy.toml | 2 +- 13 files changed, 43 insertions(+), 26 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index afab15a0..9ca9ef5a 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -22,7 +22,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: default - toolchain: 1.92.0 + toolchain: 1.95.0 override: true - name: Install packages (Linux) @@ -70,7 +70,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.92.0 + toolchain: 1.95.0 target: wasm32-unknown-unknown override: true components: clippy @@ -95,7 +95,7 @@ jobs: - uses: actions/checkout@v3 - uses: EmbarkStudios/cargo-deny-action@v2 with: - rust-version: "1.92.0" + rust-version: "1.95.0" log-level: warn command: check @@ -109,7 +109,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.92.0 + toolchain: 1.95.0 target: wasm32-unknown-unknown override: true - name: Download and install Trunk binary @@ -131,7 +131,7 @@ jobs: lfs: true - uses: dtolnay/rust-toolchain@master with: - toolchain: 1.92.0 + toolchain: 1.95.0 - name: Set up cargo cache uses: Swatinem/rust-cache@v2 diff --git a/Cargo.toml b/Cargo.toml index 772f0abd..db82594f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ members = [ [workspace.package] edition = "2024" license = "MIT OR Apache-2.0" -rust-version = "1.92" +rust-version = "1.95" version = "0.36.0" @@ -92,6 +92,7 @@ rust_2018_idioms = { level = "warn", priority = -1 } rust_2021_prelude_collisions = "warn" semicolon_in_expressions_from_macros = "warn" trivial_numeric_casts = "warn" +unexpected_cfgs = "warn" unsafe_op_in_unsafe_fn = "warn" # `unsafe_op_in_unsafe_fn` may become the default in future Rust versions: https://github.com/rust-lang/rust/issues/71668 unused_extern_crates = "warn" unused_import_braces = "warn" @@ -103,6 +104,7 @@ unused_qualifications = "allow" [workspace.lints.rustdoc] all = "warn" +broken_intra_doc_links = "warn" missing_crate_level_docs = "warn" # See also clippy.toml @@ -124,6 +126,7 @@ coerce_container_to_any = "warn" comparison_chain = "warn" dbg_macro = "warn" debug_assert_with_mut_call = "warn" +decimal_bitwise_operands = "warn" default_union_representation = "warn" derive_partial_eq_without_eq = "warn" disallowed_macros = "warn" # See clippy.toml @@ -136,9 +139,10 @@ doc_comment_double_space_linebreaks = "warn" doc_include_without_cfg = "warn" doc_link_with_quotes = "warn" doc_markdown = "warn" +duration_suboptimal_units = "warn" elidable_lifetime_names = "warn" -empty_enum = "warn" empty_enum_variants_with_brackets = "warn" +empty_enums = "warn" empty_line_after_outer_attr = "warn" enum_glob_use = "warn" equatable_if_let = "warn" @@ -154,10 +158,12 @@ flat_map_option = "warn" float_cmp_const = "warn" fn_params_excessive_bools = "warn" fn_to_numeric_cast_any = "warn" +format_push_string = "warn" from_iter_instead_of_collect = "warn" get_unwrap = "warn" if_let_mutex = "warn" ignore_without_reason = "warn" +ignored_unit_patterns = "warn" implicit_clone = "warn" implied_bounds_in_impls = "warn" imprecise_flops = "warn" @@ -190,6 +196,7 @@ lossy_float_literal = "warn" macro_use_imports = "warn" manual_assert = "warn" manual_clamp = "warn" +manual_ilog2 = "warn" manual_instant_elapsed = "warn" manual_is_power_of_two = "warn" manual_is_variant_and = "warn" @@ -208,6 +215,7 @@ mismatching_type_param_order = "warn" missing_assert_message = "warn" missing_enforced_import_renames = "warn" missing_errors_doc = "warn" +missing_fields_in_debug = "warn" missing_safety_doc = "warn" mixed_attributes_style = "warn" mut_mut = "warn" @@ -217,10 +225,13 @@ needless_continue = "warn" needless_for_each = "warn" needless_pass_by_ref_mut = "warn" needless_pass_by_value = "warn" +needless_raw_string_hashes = "warn" +needless_type_cast = "warn" negative_feature_names = "warn" non_std_lazy_statics = "warn" non_zero_suggestions = "warn" nonstandard_macro_braces = "warn" +only_used_in_recursion = "warn" option_as_ref_cloned = "warn" option_option = "warn" or_fun_call = "warn" @@ -237,11 +248,13 @@ rc_mutex = "warn" readonly_write_lock = "warn" redundant_type_annotations = "warn" ref_as_ptr = "warn" +ref_option = "warn" ref_option_ref = "warn" ref_patterns = "warn" rest_pat_in_fully_bound_structs = "warn" return_and_then = "warn" same_functions_in_if_condition = "warn" +same_length_and_capacity = "warn" self_only_used_in_recursion = "warn" semicolon_if_nothing_returned = "warn" set_contains_or_insert = "warn" @@ -259,6 +272,7 @@ suspicious_command_arg_space = "warn" suspicious_xor_used_as_pow = "warn" todo = "warn" too_long_first_doc_paragraph = "warn" +too_many_arguments = "warn" too_many_lines = "warn" trailing_empty_array = "warn" trait_duplication_in_bounds = "warn" @@ -277,6 +291,7 @@ unnecessary_safety_doc = "warn" unnecessary_self_imports = "warn" unnecessary_semicolon = "warn" unnecessary_struct_initialization = "warn" +unnecessary_trailing_comma = "warn" unnecessary_wraps = "warn" unnested_or_patterns = "warn" unused_async = "warn" diff --git a/clippy.toml b/clippy.toml index 9f217cc4..e3db8ec5 100644 --- a/clippy.toml +++ b/clippy.toml @@ -5,7 +5,7 @@ # ----------------------------------------------------------------------------- # Section identical to scripts/clippy_wasm/clippy.toml: -msrv = "1.92" +msrv = "1.95" allow-unwrap-in-tests = true diff --git a/egui_plot/src/items/bar_chart.rs b/egui_plot/src/items/bar_chart.rs index 16685233..65415c22 100644 --- a/egui_plot/src/items/bar_chart.rs +++ b/egui_plot/src/items/bar_chart.rs @@ -23,7 +23,7 @@ use crate::items::PlotGeometry; use crate::items::PlotItem; use crate::items::PlotItemBase; use crate::items::add_rulers_and_text; -use crate::label::LabelFormatter; +use crate::label::LabelFormatterFn; use crate::math::find_closest_rect; use crate::rect_elem::RectElement; @@ -210,7 +210,7 @@ impl PlotItem for BarChart { shapes: &mut Vec, cursors: &mut Vec, plot: &PlotConfig<'_>, - _: &Option>, + _: Option<&LabelFormatterFn<'_>>, ) { let bar = &self.bars[elem.index]; diff --git a/egui_plot/src/items/box_plot.rs b/egui_plot/src/items/box_plot.rs index 74c3238d..c5de980d 100644 --- a/egui_plot/src/items/box_plot.rs +++ b/egui_plot/src/items/box_plot.rs @@ -22,7 +22,7 @@ use crate::items::PlotGeometry; use crate::items::PlotItem; use crate::items::PlotItemBase; use crate::items::add_rulers_and_text; -use crate::label::LabelFormatter; +use crate::label::LabelFormatterFn; use crate::math::find_closest_rect; use crate::rect_elem::RectElement; @@ -176,7 +176,7 @@ impl PlotItem for BoxPlot { shapes: &mut Vec, cursors: &mut Vec, plot: &PlotConfig<'_>, - _: &Option>, + _: Option<&LabelFormatterFn<'_>>, ) { let box_plot = &self.boxes[elem.index]; diff --git a/egui_plot/src/items/heatmap.rs b/egui_plot/src/items/heatmap.rs index ed8d1428..94b440bd 100644 --- a/egui_plot/src/items/heatmap.rs +++ b/egui_plot/src/items/heatmap.rs @@ -24,7 +24,7 @@ use crate::items::PlotConfig; use crate::items::PlotGeometry; use crate::items::PlotItem; use crate::items::PlotItemBase; -use crate::label::LabelFormatter; +use crate::label::LabelFormatterFn; /// Default resolution for heatmap color palette pub const DEFAULT_RESOLUTION: usize = 128; @@ -459,7 +459,7 @@ impl PlotItem for Heatmap { shapes: &mut Vec, _cursors: &mut Vec, plot: &PlotConfig<'_>, - _: &Option>, + _: Option<&LabelFormatterFn<'_>>, ) { let (rect, color, text) = self.tile_view_info(plot.ui, plot.transform, elem.index); let mut mesh = Mesh::default(); diff --git a/egui_plot/src/items/mod.rs b/egui_plot/src/items/mod.rs index 6781a99f..95d381bb 100644 --- a/egui_plot/src/items/mod.rs +++ b/egui_plot/src/items/mod.rs @@ -41,7 +41,7 @@ pub use crate::items::series::Line; pub use crate::items::span::Span; pub use crate::items::text::Text; use crate::label::HoverPosition; -use crate::label::LabelFormatter; +use crate::label::LabelFormatterFn; use crate::rect_elem::RectElement; mod arrows; @@ -175,7 +175,7 @@ pub trait PlotItem { shapes: &mut Vec, cursors: &mut Vec, plot: &PlotConfig<'_>, - label_formatter: &Option>, + label_formatter: Option<&LabelFormatterFn<'_>>, ) { let points = match self.geometry() { PlotGeometry::Points(points) => points, @@ -283,7 +283,7 @@ pub(super) fn rulers_and_tooltip_at_value( nearest_point: Option<(&str, usize)>, plot: &PlotConfig<'_>, cursors: &mut Vec, - label_formatter: &Option>, + label_formatter: Option<&LabelFormatterFn<'_>>, ) { // Add crosshair rulers if enabled if plot.show_crosshair { diff --git a/egui_plot/src/label.rs b/egui_plot/src/label.rs index ea1e2bdd..d982723f 100644 --- a/egui_plot/src/label.rs +++ b/egui_plot/src/label.rs @@ -16,7 +16,8 @@ pub fn format_number(number: f64, num_decimals: usize) -> String { } } -type LabelFormatterFn<'a> = dyn Fn(&HoverPosition<'_>) -> Option + 'a; +/// The unboxed form of [`LabelFormatter`]. +pub type LabelFormatterFn<'a> = dyn Fn(&HoverPosition<'_>) -> Option + 'a; /// Optional label formatter function for customizing hover labels. pub type LabelFormatter<'a> = Box>; diff --git a/egui_plot/src/lib.rs b/egui_plot/src/lib.rs index 91bbae06..ac7c4033 100644 --- a/egui_plot/src/lib.rs +++ b/egui_plot/src/lib.rs @@ -63,6 +63,7 @@ pub use crate::items::Text; pub use crate::items::VLine; pub use crate::label::HoverPosition; pub use crate::label::LabelFormatter; +pub use crate::label::LabelFormatterFn; pub use crate::label::default_label_formatter; pub use crate::label::format_number; pub use crate::memory::PlotMemory; diff --git a/egui_plot/src/plot.rs b/egui_plot/src/plot.rs index d993f974..f5a27e08 100644 --- a/egui_plot/src/plot.rs +++ b/egui_plot/src/plot.rs @@ -1424,9 +1424,9 @@ impl<'a> Plot<'a> { response: &Response, transform: &PlotTransform, painter: &Painter, - coordinates_formatter: &Option<(Corner, CoordinatesFormatter<'_>)>, + coordinates_formatter: Option<&(Corner, CoordinatesFormatter<'_>)>, ) { - if let Some((corner, formatter)) = coordinates_formatter.as_ref() { + if let Some((corner, formatter)) = coordinates_formatter { let hover_pos = response.hover_pos(); if let Some(pointer) = hover_pos { let font_id = TextStyle::Monospace.resolve(ui.style()); @@ -1591,7 +1591,7 @@ impl<'a> Plot<'a> { shapes, &mut cursors, &plot, - &self.label_formatter, + self.label_formatter.as_deref(), ); Some(item.id()) } else { @@ -1602,7 +1602,7 @@ impl<'a> Plot<'a> { None, &plot, &mut cursors, - &self.label_formatter, + self.label_formatter.as_deref(), ); None }; @@ -1679,7 +1679,7 @@ impl<'a> Plot<'a> { &plot_ui.response, &mem.transform, &painter, - &self.coordinates_formatter, + self.coordinates_formatter.as_ref(), ); // Show legend and update memory diff --git a/rust-toolchain b/rust-toolchain index ce5b5098..7fb2669f 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -5,6 +5,6 @@ # to the user in the error, instead of "error: invalid channel name '[toolchain]'". [toolchain] -channel = "1.92.0" +channel = "1.95.0" components = ["rustfmt", "clippy"] targets = ["wasm32-unknown-unknown"] diff --git a/scripts/check.sh b/scripts/check.sh index 640f31da..ee92ad51 100755 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -9,7 +9,7 @@ set -x # Checks all tests, lints etc. # Basically does what the CI does. -cargo +1.92.0 install --quiet typos-cli +cargo +1.95.0 install --quiet typos-cli export RUSTFLAGS="-D warnings" export RUSTDOCFLAGS="-D warnings" # https://github.com/emilk/egui/pull/1454 diff --git a/scripts/clippy_wasm/clippy.toml b/scripts/clippy_wasm/clippy.toml index 1feb2bc8..920856ce 100644 --- a/scripts/clippy_wasm/clippy.toml +++ b/scripts/clippy_wasm/clippy.toml @@ -8,7 +8,7 @@ # ----------------------------------------------------------------------------- # Section identical to the main clippy.toml: -msrv = "1.92" +msrv = "1.95" allow-unwrap-in-tests = true From 4ce6dcbbe326771300b00b7ada7213539a5b323f Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 3 Aug 2026 10:12:15 +0200 Subject: [PATCH 2/3] Fix cargo-deny failures * `cargo update -p crossbeam-epoch` fixes RUSTSEC-2026-0204. * Ignore RUSTSEC-2026-0192 (`ttf-parser` unmaintained) and RUSTSEC-2026-0194/0195 (`quick-xml` DoS). All three come in via `eframe`, which is only a dev-dependency, and only on Linux, and none can be upgraded without upstream doing so first. * Drop the ignore for RUSTSEC-2024-0436, since `paste` is no longer in our dependency tree at all (cargo-deny warned about this). Co-Authored-By: Claude Opus 5 (1M context) --- Cargo.lock | 4 ++-- deny.toml | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 45ba4cd5..ac51fd4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -867,9 +867,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.18" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" dependencies = [ "crossbeam-utils", ] diff --git a/deny.toml b/deny.toml index c059fd7d..a6d13211 100644 --- a/deny.toml +++ b/deny.toml @@ -31,7 +31,11 @@ all-features = true [advisories] version = 2 ignore = [ - "RUSTSEC-2024-0436", # https://rustsec.org/advisories/RUSTSEC-2024-0436 - paste is unmaintained - https://github.com/dtolnay/paste + # All of these come in via `eframe`, which is only a dev-dependency of ours, + # and only on Linux. We cannot upgrade them without upstream doing so first. + "RUSTSEC-2026-0192", # https://rustsec.org/advisories/RUSTSEC-2026-0192 - ttf-parser is unmaintained, via ab_glyph -> sctk-adwaita -> winit. There is no fixed version. + "RUSTSEC-2026-0194", # https://rustsec.org/advisories/RUSTSEC-2026-0194 - quick-xml DoS, via zbus_xml -> accesskit_unix. Fixed in 0.41, but zbus_xml requires 0.39. + "RUSTSEC-2026-0195", # https://rustsec.org/advisories/RUSTSEC-2026-0195 - quick-xml DoS, via zbus_xml -> accesskit_unix. Fixed in 0.41, but zbus_xml requires 0.39. ] From f6a6f974289b2937e3e5e508902c56e1e805c978 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 3 Aug 2026 10:24:58 +0200 Subject: [PATCH 3/3] Modernize code using APIs newly stable in Rust 1.94 * `slice::array_windows` (1.94) replaces `windows(2)` in `Line`, so the pairs are destructured instead of indexed. * `f32::consts::GOLDEN_RATIO` (1.94) replaces two hand-rolled `(5.0_f32.sqrt() - 1.0) / 2.0`. Co-Authored-By: Claude Opus 5 (1M context) --- egui_plot/src/aesthetics.rs | 2 +- egui_plot/src/items/series.rs | 20 ++++++++++---------- egui_plot/src/plot.rs | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/egui_plot/src/aesthetics.rs b/egui_plot/src/aesthetics.rs index c84d2eea..d02564fa 100644 --- a/egui_plot/src/aesthetics.rs +++ b/egui_plot/src/aesthetics.rs @@ -67,7 +67,7 @@ impl LineStyle { if highlight { stroke.width *= 2.0; } - let golden_ratio = (5.0_f32.sqrt() - 1.0) / 2.0; // 0.61803398875 + let golden_ratio = std::f32::consts::GOLDEN_RATIO - 1.0; // 0.61803398875 shapes.extend(Shape::dashed_line( &line, Stroke::new(stroke.width, path_stroke_color), diff --git a/egui_plot/src/items/series.rs b/egui_plot/src/items/series.rs index cd634cae..f77ce8ba 100644 --- a/egui_plot/src/items/series.rs +++ b/egui_plot/src/items/series.rs @@ -218,12 +218,12 @@ impl PlotItem for Line<'_> { let expected_intersections = 20; mesh.reserve_triangles((n_values - 1) * 2); mesh.reserve_vertices(n_values * 2 + expected_intersections); - values_tf.windows(2).for_each(|w| { - let fill_color = fill_color_for_point(w[0]); + for [prev, next] in values_tf.array_windows::<2>() { + let fill_color = fill_color_for_point(*prev); let i = mesh.vertices.len() as u32; - mesh.colored_vertex(w[0], fill_color); - mesh.colored_vertex(pos2(w[0].x, y), fill_color); - if let Some(x) = y_intersection(&w[0], &w[1], y) { + mesh.colored_vertex(*prev, fill_color); + mesh.colored_vertex(pos2(prev.x, y), fill_color); + if let Some(x) = y_intersection(prev, next, y) { let point = pos2(x, y); mesh.colored_vertex(point, fill_color_for_point(point)); mesh.add_triangle(i, i + 1, i + 2); @@ -232,7 +232,7 @@ impl PlotItem for Line<'_> { mesh.add_triangle(i, i + 1, i + 2); mesh.add_triangle(i + 1, i + 2, i + 3); } - }); + } let last = values_tf[n_values - 1]; let fill_color = fill_color_for_point(last); mesh.colored_vertex(last, fill_color); @@ -260,11 +260,11 @@ impl PlotItem for Line<'_> { } points - .windows(2) + .array_windows::<2>() .enumerate() - .map(|(i, w)| { - let p0 = transform.position_from_point(&w[0]); - let p1 = transform.position_from_point(&w[1]); + .map(|(i, [v0, v1])| { + let p0 = transform.position_from_point(v0); + let p1 = transform.position_from_point(v1); let dist_sq = dist_sq_to_segment(point, [p0, p1]); // Pick the closer endpoint so the tooltip shows a real data point let index = if point.distance_sq(p0) <= point.distance_sq(p1) { diff --git a/egui_plot/src/plot.rs b/egui_plot/src/plot.rs index f5a27e08..687a4dd9 100644 --- a/egui_plot/src/plot.rs +++ b/egui_plot/src/plot.rs @@ -1890,7 +1890,7 @@ impl<'a> PlotUi<'a> { fn auto_color(&mut self) -> Color32 { let i = self.next_auto_color_idx; self.next_auto_color_idx += 1; - let golden_ratio = (5.0_f32.sqrt() - 1.0) / 2.0; // 0.61803398875 + let golden_ratio = std::f32::consts::GOLDEN_RATIO - 1.0; // 0.61803398875 let h = i as f32 * golden_ratio; Hsva::new(h, 0.85, 0.5, 1.0).into() // TODO(#165): OkLab or some other perspective color space }