镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Weaken the closure parameters in calls using rust-call #46

Closed

Description

@coord-e

4e2836c

fn apply<F>(f: F) -> i32
where
    F: FnMut(i32) -> i32,
{
    f(1)
}

fn main() {
    let x = 1;
    let closure = |y: i32| x + y;
    let result = apply(closure);
    assert!(result == 2);
}

In the example above, the closure only refers to the outer environment via an immutable reference. This results in a MIR function for the closure that takes the environment by immutable reference:

// MIR for `main::{closure#0}` after runtime-optimized

fn main::{closure#0}(_1: &{closure@closure_param_weaken.rs:10:19: 10:27}, _2: i32) -> i32 {
    debug y => _2;
    debug x => (*((*_1).0: &i32));
    let mut _0: i32;
    let mut _3: i32;
    let mut _4: &i32;

    bb0: {
        _4 = deref_copy ((*_1).0: &i32);
        _3 = (*_4);
        _0 = Add(move _3, _2);
        return;
    }
}

However, in apply, the closure f is called via FnMut (call_mut), so it takes &mut even though FnMut::call_mut resolves to main::{closure#0}:

// MIR for `apply` after runtime-optimized

fn apply(_1: F) -> i32 {
    debug f => _1;
    let mut _0: i32;
    let mut _2: &mut F;
    let mut _3: (i32,);

    bb0: {
        _2 = &mut _1;
        _3 = (const 1_i32,);
        _0 = <F as FnMut<(i32,)>>::call_mut(move _2, move _3) -> [return: bb1, unwind: bb3];
    }

    bb1: {
        drop(_1) -> [return: bb2, unwind continue];
    }

    bb2: {
        return;
    }

    bb3 (cleanup): {
        drop(_1) -> [return: bb4, unwind terminate(cleanup)];
    }

    bb4 (cleanup): {
        resume;
    }
}

This leads to a type mismatch during subtyping.

thread 'rustc' panicked at src/rty/subtyping.rs:117:18:
inconsistent types: got=&mut (own (&immut int),
), expected=&immut (own (&immut int),
)
stack backtrace:
   0: rust_begin_unwind
             at /rustc/9c3ad802d9b9633d60d3a74668eb1be819212d34/library/std/src/panicking.rs:645:5
   1: core::panicking::panic_fmt
             at /rustc/9c3ad802d9b9633d60d3a74668eb1be819212d34/library/core/src/panicking.rs:72:14
   2: <C as thrust::rty::subtyping::Subtyping>::relate_sub_type
             at ./src/rty/subtyping.rs:117:18
   3: thrust::analyze::basic_block::Analyzer::relate_fn_sub_type
             at ./src/analyze/basic_block.rs:155:28
   4: thrust::analyze::basic_block::Analyzer::type_call
             at ./src/analyze/basic_block.rs:595:27
   5: thrust::analyze::basic_block::Analyzer::analyze_terminator_binds
             at ./src/analyze/basic_block.rs:785:13
   6: thrust::analyze::basic_block::Analyzer::run
             at ./src/analyze/basic_block.rs:1129:9
   7: thrust::analyze::local_def::Analyzer::analyze_basic_blocks
             at ./src/analyze/local_def.rs:683:13
   8: thrust::analyze::local_def::Analyzer::run
             at ./src/analyze/local_def.rs:816:9
   9: thrust::analyze::Analyzer::def_ty_with_args
             at ./src/analyze.rs:343:9
  10: thrust::analyze::basic_block::Analyzer::type_call
             at ./src/analyze/basic_block.rs:582:13
  11: thrust::analyze::basic_block::Analyzer::analyze_terminator_binds
             at ./src/analyze/basic_block.rs:785:13
  12: thrust::analyze::basic_block::Analyzer::run
             at ./src/analyze/basic_block.rs:1129:9
  13: thrust::analyze::local_def::Analyzer::analyze_basic_blocks
             at ./src/analyze/local_def.rs:683:13
  14: thrust::analyze::local_def::Analyzer::run
             at ./src/analyze/local_def.rs:816:9
  15: thrust::analyze::crate_::Analyzer::analyze_local_defs
             at ./src/analyze/crate_.rs:135:13
  16: thrust::analyze::crate_::Analyzer::run
             at ./src/analyze/crate_.rs:240:9
  17: <thrust_rustc::CompilerCalls as rustc_driver_impl::Callbacks>::after_analysis::{{closure}}
             at ./src/main.rs:54:13
  18: rustc_middle::ty::context::GlobalCtxt::enter::{{closure}}
             at /rustc/9c3ad802d9b9633d60d3a74668eb1be819212d34/compiler/rustc_middle/src/ty/context.rs:649:37
  19: rustc_middle::ty::context::tls::enter_context::{{closure}}
             at /rustc/9c3ad802d9b9633d60d3a74668eb1be819212d34/compiler/rustc_middle/src/ty/context/tls.rs:82:9
  20: std::thread::local::LocalKey<T>::try_with
             at /rustc/9c3ad802d9b9633d60d3a74668eb1be819212d34/library/std/src/thread/local.rs:284:16
  21: std::thread::local::LocalKey<T>::with
             at /rustc/9c3ad802d9b9633d60d3a74668eb1be819212d34/library/std/src/thread/local.rs:260:9
  22: rustc_middle::ty::context::tls::enter_context
             at /rustc/9c3ad802d9b9633d60d3a74668eb1be819212d34/compiler/rustc_middle/src/ty/context/tls.rs:79:9
  23: rustc_middle::ty::context::GlobalCtxt::enter
             at /rustc/9c3ad802d9b9633d60d3a74668eb1be819212d34/compiler/rustc_middle/src/ty/context.rs:649:9
  24: rustc_interface::queries::QueryResult<&rustc_middle::ty::context::GlobalCtxt>::enter
             at /rustc/9c3ad802d9b9633d60d3a74668eb1be819212d34/compiler/rustc_interface/src/queries.rs:71:9
  25: <thrust_rustc::CompilerCalls as rustc_driver_impl::Callbacks>::after_analysis
             at ./src/main.rs:51:9
  26: rustc_interface::interface::run_compiler::<core::result::Result<(), rustc_span::ErrorGuaranteed>, rustc_driver_impl::run_compiler::{closure#0}>::{closure#0}
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

Activity

  1. coord-e commented on Feb 11, 2026

    @coord-e
    OwnerAuthor

    #48 addresses the case mentioned above, but calling an FnMut closure via FnOnce seems to require more work.

    fn apply<F>(f: F) -> i32
    where
        F: FnOnce(i32) -> i32,
    {
        f(1)
    }
    
    fn main() {
        let mut x = 1;
        let closure = |y: i32| {
            x += 1;
            y + x
        };
        let result = apply(closure);
        assert!(result == 3 && x == 2);
    }

    The following patch fixes the panics, but it cannot prove the assertion (incompleteness), so further investigation is required.

    diff --git a/src/analyze/basic_block/visitor/rust_call.rs b/src/analyze/basic_block/visitor/rust_call.rs
    index 5b4a24d..18f2b3e 100644
    --- a/src/analyze/basic_block/visitor/rust_call.rs
    +++ b/src/analyze/basic_block/visitor/rust_call.rs
    @@ -8,6 +8,17 @@ pub struct RustCallVisitor<'a, 'tcx, 'ctx> {
    
     // TODO: consolidate logic with ReborrowVisitor
     impl<'tcx> RustCallVisitor<'_, 'tcx, '_> {
    +    fn insert_mut_borrow(&mut self, place: mir::Place<'tcx>, inner_ty: mir_ty::Ty<'tcx>) -> Local {
    +        let r = mir_ty::Region::new_from_kind(self.tcx, mir_ty::RegionKind::ReErased);
    +        let ty = mir_ty::Ty::new_mut_ref(self.tcx, r, inner_ty);
    +        let decl = mir::LocalDecl::new(ty, Default::default()).immutable();
    +        let new_local = self.analyzer.local_decls.push(decl);
    +        let new_local_ty = self.analyzer.borrow_place_(place, inner_ty);
    +        self.analyzer.bind_local(new_local, new_local_ty);
    +        tracing::info!(old_place = ?place, ?new_local, "implicitly borrowed");
    +        new_local
    +    }
    +
         fn insert_immut_borrow(
             &mut self,
             place: mir::Place<'tcx>,
    @@ -108,7 +119,10 @@ impl<'a, 'tcx, 'ctx> mir::visit::MutVisitor<'tcx> for RustCallVisitor<'a, 'tcx,
                         if arg_closure_ty.ref_mutability().is_none()
                     ) {
                         // case 3: {closure} -> &mut {closure}
    -                    unimplemented!();
    +                    let borrowed_closure_local =
    +                        self.insert_mut_borrow(arg_closure_place, arg_closure_ty);
    +                    args[0].node = mir::Operand::Move(borrowed_closure_local.into());
    +                    tracing::debug!("applied mut-borrow for closure argument");
                     }
                 }
             }
    diff --git a/src/analyze/local_def.rs b/src/analyze/local_def.rs
    index 826d906..2e395b9 100644
    --- a/src/analyze/local_def.rs
    +++ b/src/analyze/local_def.rs
    @@ -531,6 +531,24 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> {
                     self.super_operand(operand, location);
                 }
    
    +            fn visit_terminator(
    +                &mut self,
    +                terminator: &mir::Terminator<'tcx>,
    +                location: mir::Location,
    +            ) {
    +                if let mir::TerminatorKind::Call { func, args, .. } = &terminator.kind {
    +                    if let Some((def_id, generic_args)) = func.const_fn_def() {
    +                        if let Some(trait_did) = self.tcx.trait_of_item(def_id) {
    +                            if trait_did == self.tcx.lang_items().fn_once_trait().unwrap() {
    +                                // TODO: check generic_args[0].0 fn sig is &mut
    +                                self.locals.insert(args[0].node.place().unwrap().local);
    +                            }
    +                        }
    +                    }
    +                }
    +                self.super_terminator(terminator, location);
    +            }
    +
                 fn visit_assign(
                     &mut self,
                     place: &mir::Place<'tcx>,
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions