2

就像下面的 rust 代码:while循环编译并运行良好,但for iter版本没有编译,由于错误:

error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
  --> src/main.rs:22:9
   |
20 |     for i in v.iter() {
   |              --------
   |              |
   |              immutable borrow occurs here
   |              immutable borrow later used here
21 |         println!("v[i]: {}", i);
22 |         v.push(20);
   |         ^^^^^^^^^^ mutable borrow occurs here

error: aborting due to previous error

但是据了解,whileloop也有同样的场景,len也是不可变借用,为什么和as借用可变get不冲突呢?push请指教我在这里缺少什么理解,非常感谢您的启发!

fn main() {
    let mut v = Vec::new();
    
    
    v.push(1);
    v.push(2);
    v.push(3);
    v.push(4);
    
    let mut i = 0;
    
    while i < v.len() && i < 10 {
        
        v.push(20);
        println!("v[i]: {:?}", v.get(i));
        i += 1;
    }
    
    // for i in v.iter() {
    //     println!("v[i]: {}", i);
    //     v.push(20);
        
    // }
    
}
4

3 回答 3

3

您的for代码版本大致相当于以下内容:

fn main() {
    let mut v = Vec::new();
    v.push(1);
    v.push(2);
    v.push(3);
    v.push(4);
    
    let mut it = v.iter();
    while let Some(i) = it.next() {
        println!("v[i]: {}", i);
        v.push(20);
    }
}

操场

如果你尝试编译它,你会得到一个可能更有意义的错误:

error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
  --> src/main.rs:11:9
   |
8  |     let mut it = v.iter();
   |                  - immutable borrow occurs here
9  |     while let Some(i) = it.next() {
   |                         -- immutable borrow later used here
10 |         println!("v[i]: {}", i);
11 |         v.push(20);
   |         ^^^^^^^^^^ mutable borrow occurs here

迭代器v在循环的整个持续时间内不可变地借用,因此您不能在循环内进行任何可变借用。

当然,即使你能做到这一点,你最终也会陷入无限循环,因为你不断地追加另一个项目。

于 2020-08-21T16:31:16.067 回答
1

简单地说,当你调用时.iter(),你创建了一个新的对象(一个迭代器),它借用了你的向量(不可变),并且一个一个地给出了元素,这意味着你实际上是借用v整个循环的时间。另一方面,当您通过访问它时,.get(i)您直接从向量中借用一个元素,因此当您使用push.

这种限制的原因很简单:想象你的实际for循环确实编译了,它会永远运行(为了防止在while循环中发生这种情况,你必须添加人工条件i<10!),而这显然不是预期的目标(或者如果否则你显然会这样做,例如使用while letorloop语句),Rust 试图阻止你“朝自己的腿开枪”,因为你真的不知道如何做你想做的事,并尝试错误方法。

做你想做的事,你可以这样做:

for i in 0..v.len() {
    v.push(20)
}

因为v.len()调用不会v在整个for循环的时间内借用,而只是在开始时。

于 2020-08-21T16:21:31.707 回答
0

Rust 借用检查器定义了一组规则,以哪些可能的模式(可变或不可变)可以借用什么。它做得更多,即处理生命周期,如果你不借,你就搬家。

对于有抱负的 Rust 程序员来说,处理借用检查器强加的规则是痛苦的山谷(也就是学习曲线),他们必须通过才能变得富有成效。

一次,迭代器不可变地借用向量(并在循环期间保持它),借用检查器抱怨尝试变异。

如果你像我一样是一个横向思考者……每当我看到一种新语言的东西时,我都无法完全理解,我会尝试用我知道的语言写出那个“奇怪的东西”的教育近似已经好多了。

因此,希望它有所帮助,您可以找到一个非常简化和大胆的近似值,即 rust 在 C++ 中的作用(在编译时)。只有 - 在我的 C++ 代码中,它发生在运行时并且违反规则会导致抛出异常。

我们使用的实例的“状态”(std::vector<int32_t>在本例中为 a),关于可变性以及根据我们仓促发明的一组借用规则(可能类似于也可能不类似于 rusts)我们仍然可以做什么,由不同的类型表示(Managed<T>::ConstRefManaged<T>::MutableRef)。状态适用的范围是 lambda 函数的范围,它们用作“智能范围”。main()在下面的代码中试图复制for i in vec.iter() { .. }场景。

也许从这个角度看问题对某人有帮助。

#include <iostream>
#include <cstdint>
#include <vector>
#include <string>
#include <stdexcept>
#include <sstream>

template<class T>
void notNull(const T* p)
{
  if(nullptr == p)
    throw std::invalid_argument("pointer is null!");
};

enum class Demand : uint8_t
{ CONST
, MUTABLE
};

inline
std::string
exinfo
(const char* message
, const char * file
, int line
 )
{
  std::ostringstream os;
  os << file << ":" << line << std::endl
     << message << std::endl;
  return os.str();
}

class borrow_error
  : public std::logic_error
{
public:
  explicit borrow_error(const std::string& what_arg)
    : logic_error(what_arg)
  {}
  explicit borrow_error(const char* what_arg)
    : logic_error(what_arg)
  {}
};
class mutable_borrow_after_const_borrow
  : public borrow_error
{
public:
  mutable_borrow_after_const_borrow(const char* file, int line)
    : borrow_error
      (exinfo("mutable borrow after const borrow",file,line))
  {}
};

#define THROW_MUTABLE_BORROW_AFTER_CONST_BORROW \
  throw mutable_borrow_after_const_borrow(__FILE__,__LINE__)


class const_borrow_after_mutable_borrow
  :public borrow_error
{
public:
  const_borrow_after_mutable_borrow(const char* file, int line)
    : borrow_error
      (exinfo("const borrow after mutable borrow",file,line))
  {}
};

#define THROW_CONST_BORROW_AFTER_MUTABLE_BORROW \
  throw const_borrow_after_mutable_borrow(__FILE__,__LINE__)

class multiple_mutable_borrows
  :public borrow_error
{
public:
  multiple_mutable_borrows(const char* file, int line)
    : borrow_error
      (exinfo("more than one mutable borrow",file,line))
  {}
};

#define THROW_MULTIPLE_MUTABLE_BORROWS \
  throw multiple_mutable_borrows(__FILE__,__LINE__)

class mutable_access_to_const_attempt
  : public borrow_error
{
public:
  mutable_access_to_const_attempt(const char* file, int line)
    : borrow_error
      (exinfo("request to mutate the immutable.",file,line))
  {}
};

#define THROW_MUTABLE_ACCESS_TO_CONST_ATTEMPT \
  throw  mutable_access_to_const_attempt(__FILE__,__LINE__)


template <class T>
struct Managed
{
  using this_type = Managed<T>;
  using managed_type = T;
  struct ConstRef
  {
    this_type * origin;
    ConstRef(this_type *org, const char* file, int line)
      : origin{org}
    {
      notNull(origin);
      // if( 0 != origin->mutableAccess )
      // {
      //    throw const_borrow_after_mutable_borrow(file,line);
      // }
      origin->constAccess++;
    }
    ~ConstRef()
    {
      origin->constAccess--;
    }
    operator const T&()
    {
      return origin->instance;
    }
  };
  struct MutableRef
  {
    this_type *origin;
    MutableRef(this_type *org, const char* file, int line)
      : origin{org}
    {
      notNull(origin);
      if(origin->instanceMode == Demand::CONST)
      {
    throw mutable_access_to_const_attempt(file,line);
      }
      if( 0 != origin->constAccess )
      {
    throw mutable_borrow_after_const_borrow(file,line);
      }
      // also allow max 1 mutator
      if( 0 != origin->mutableAccess )
      {
    throw multiple_mutable_borrows(file,line);
      }
      origin->mutableAccess++;
    }
    ~MutableRef()
    {
      origin->mutableAccess--;
    }
    operator T&()
    {
      return origin->instance;
    }
  };
  
  Demand instanceMode;
  int32_t constAccess;
  int32_t mutableAccess;
  T& instance;
  Managed(T& inst, Demand demand = Demand::CONST)
    : instanceMode{demand}
    , constAccess{0}
    , mutableAccess{0}
    , instance{inst}
  {
  }
};

template <typename T, class F>
auto
borrow_const
( T& instance
, F body
, const char * file
, int line
) -> void
{
  typename T::ConstRef arg{&instance, file, line};
  body(arg);
}

#define BORROW_CONST(inst,body) \
  borrow_const((inst),(body),__FILE__,__LINE__)

template <typename T, class F>
auto
borrow_mut
( T& instance
, F body
, const char * file
, int line
) -> void
{
  typename T::MutableRef arg{&instance, file, line};
  body(arg);
};

#define BORROW_MUT(inst,body) \
  borrow_mut((inst),(body),__FILE__,__LINE__)


using VecI32 = std::vector<int32_t>;
using ManagedVector = Managed<VecI32>;

int main(int argc, const char *argv[])
{
  VecI32 myVector;
  ManagedVector vec{myVector,Demand::MUTABLE};

  try
  {
    BORROW_MUT
      ( vec
      , [] (ManagedVector::MutableRef& vecRef) -> void
    {
      static_cast<VecI32&>(vecRef).push_back(1);
      static_cast<VecI32&>(vecRef).push_back(2);
      // during iteration, changing targets are bad...
      // if you borrow the vector to an iterator,
      // the "state" of the vector becomes 'immutable'.
      BORROW_CONST
        ( *(vecRef.origin)
        , [] (typename ManagedVector::ConstRef& vecRef) -> void
          {
        for( auto i : static_cast<const VecI32&>(vecRef) )
        {
          std::cout << i << std::endl;
          // Enforced by the rust compiler established
          // borrow rules,
          // it is not allowed to borrow mut from something
          // immutable.
          // Thus, trying to change what we iterate over...
          // should be prevented by the borrow checker.
          BORROW_MUT
            ( *(vecRef.origin)
            , [] (typename ManagedVector::MutableRef& vecRef)
                 -> void
              {
            // next line should throw!
            // but our BORROW_MUT throws first.
            static_cast<VecI32&>(vecRef).push_back(3); 
              });
        }
          });
    });
  }
  catch(borrow_error& berr)
  {
    std::cout << "borrow error: " << berr.what() << std::endl;
  }
  catch(std::logic_error& lerr)
  {
    std::cout << "logic error: " << lerr.what() << std::endl;
  }
  catch(std::exception& ex)
  {
    std::cout << "std::exception: " << ex.what() << std::endl;
  }
  catch(...)
  {
    std::cout << "We are taking horrible, HORRIBLE damage!" << std::endl;
  }
  
  return 0;
}

于 2020-08-21T21:32:15.203 回答